• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 **
3 ** Copyright 2007, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 package com.android.packageinstaller;
18 
19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import android.Manifest;
22 import android.annotation.NonNull;
23 import android.annotation.StringRes;
24 import android.app.AlertDialog;
25 import android.app.AppGlobals;
26 import android.app.AppOpsManager;
27 import android.app.Dialog;
28 import android.app.DialogFragment;
29 import android.app.admin.DevicePolicyManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.ContentResolver;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.IPackageManager;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.PackageInstaller;
39 import android.content.pm.PackageManager;
40 import android.content.pm.PackageManager.NameNotFoundException;
41 import android.content.pm.PackageParser;
42 import android.content.pm.PackageUserState;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.Process;
46 import android.os.UserManager;
47 import android.provider.Settings;
48 import android.util.Log;
49 import android.view.View;
50 import android.widget.Button;
51 
52 import com.android.internal.app.AlertActivity;
53 
54 import java.io.File;
55 
56 /**
57  * This activity is launched when a new application is installed via side loading
58  * The package is first parsed and the user is notified of parse errors via a dialog.
59  * If the package is successfully parsed, the user is notified to turn on the install unknown
60  * applications setting. A memory check is made at this point and the user is notified of out
61  * of memory conditions if any. If the package is already existing on the device,
62  * a confirmation dialog (to replace the existing package) is presented to the user.
63  * Based on the user response the package is then installed by launching InstallAppConfirm
64  * sub activity. All state transitions are handled in this activity
65  */
66 public class PackageInstallerActivity extends AlertActivity {
67     private static final String TAG = "PackageInstaller";
68 
69     private static final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
70 
71     static final String SCHEME_PACKAGE = "package";
72 
73     static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE";
74     static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
75     private static final String ALLOW_UNKNOWN_SOURCES_KEY =
76             PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
77 
78     private int mSessionId = -1;
79     private Uri mPackageURI;
80     private Uri mOriginatingURI;
81     private Uri mReferrerURI;
82     private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
83     private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
84 
85     private boolean localLOGV = false;
86     PackageManager mPm;
87     IPackageManager mIpm;
88     AppOpsManager mAppOpsManager;
89     UserManager mUserManager;
90     PackageInstaller mInstaller;
91     PackageInfo mPkgInfo;
92     String mCallingPackage;
93     ApplicationInfo mSourceInfo;
94 
95     // ApplicationInfo object primarily used for already existing applications
96     private ApplicationInfo mAppInfo = null;
97 
98     // Buttons to indicate user acceptance
99     private Button mOk;
100 
101     private PackageUtil.AppSnippet mAppSnippet;
102 
103     static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
104 
105     // Dialog identifiers used in showDialog
106     private static final int DLG_BASE = 0;
107     private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
108     private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
109     private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
110     private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
111     private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
112     private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
113     private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
114     private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
115 
116     // If unknown sources are temporary allowed
117     private boolean mAllowUnknownSources;
118 
119     // Would the mOk button be enabled if this activity would be resumed
120     private boolean mEnableOk = false;
121 
startInstallConfirm()122     private void startInstallConfirm() {
123         View viewToEnable;
124 
125         if (mAppInfo != null) {
126             viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
127                     ? requireViewById(R.id.install_confirm_question_update_system)
128                     : requireViewById(R.id.install_confirm_question_update);
129         } else {
130             // This is a new application with no permissions.
131             viewToEnable = requireViewById(R.id.install_confirm_question);
132         }
133 
134         viewToEnable.setVisibility(View.VISIBLE);
135 
136         mEnableOk = true;
137         mOk.setEnabled(true);
138     }
139 
140     /**
141      * Replace any dialog shown by the dialog with the one for the given {@link #createDialog id}.
142      *
143      * @param id The dialog type to add
144      */
showDialogInner(int id)145     private void showDialogInner(int id) {
146         DialogFragment currentDialog =
147                 (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
148         if (currentDialog != null) {
149             currentDialog.dismissAllowingStateLoss();
150         }
151 
152         DialogFragment newDialog = createDialog(id);
153         if (newDialog != null) {
154             newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
155         }
156     }
157 
158     /**
159      * Create a new dialog.
160      *
161      * @param id The id of the dialog (determines dialog type)
162      *
163      * @return The dialog
164      */
createDialog(int id)165     private DialogFragment createDialog(int id) {
166         switch (id) {
167             case DLG_PACKAGE_ERROR:
168                 return SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
169             case DLG_OUT_OF_SPACE:
170                 return OutOfSpaceDialog.newInstance(
171                         mPm.getApplicationLabel(mPkgInfo.applicationInfo));
172             case DLG_INSTALL_ERROR:
173                 return InstallErrorDialog.newInstance(
174                         mPm.getApplicationLabel(mPkgInfo.applicationInfo));
175             case DLG_NOT_SUPPORTED_ON_WEAR:
176                 return NotSupportedOnWearDialog.newInstance();
177             case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
178                 return SimpleErrorDialog.newInstance(
179                         R.string.install_apps_user_restriction_dlg_text);
180             case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
181                 return SimpleErrorDialog.newInstance(
182                         R.string.unknown_apps_user_restriction_dlg_text);
183             case DLG_EXTERNAL_SOURCE_BLOCKED:
184                 return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage);
185             case DLG_ANONYMOUS_SOURCE:
186                 return AnonymousSourceDialog.newInstance();
187         }
188         return null;
189     }
190 
191     @Override
onActivityResult(int request, int result, Intent data)192     public void onActivityResult(int request, int result, Intent data) {
193         if (request == REQUEST_TRUST_EXTERNAL_SOURCE && result == RESULT_OK) {
194             // The user has just allowed this package to install other packages (via Settings).
195             mAllowUnknownSources = true;
196 
197             // Log the fact that the app is requesting an install, and is now allowed to do it
198             // (before this point we could only log that it's requesting an install, but isn't
199             // allowed to do it yet).
200             int appOpCode =
201                     AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
202             mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage);
203 
204             DialogFragment currentDialog =
205                     (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
206             if (currentDialog != null) {
207                 currentDialog.dismissAllowingStateLoss();
208             }
209 
210             initiateInstall();
211         } else {
212             finish();
213         }
214     }
215 
getPackageNameForUid(int sourceUid)216     private String getPackageNameForUid(int sourceUid) {
217         String[] packagesForUid = mPm.getPackagesForUid(sourceUid);
218         if (packagesForUid == null) {
219             return null;
220         }
221         if (packagesForUid.length > 1) {
222             if (mCallingPackage != null) {
223                 for (String packageName : packagesForUid) {
224                     if (packageName.equals(mCallingPackage)) {
225                         return packageName;
226                     }
227                 }
228             }
229             Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
230         }
231         return packagesForUid[0];
232     }
233 
isInstallRequestFromUnknownSource(Intent intent)234     private boolean isInstallRequestFromUnknownSource(Intent intent) {
235         if (mCallingPackage != null && intent.getBooleanExtra(
236                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
237             if (mSourceInfo != null) {
238                 if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
239                         != 0) {
240                     // Privileged apps can bypass unknown sources check if they want.
241                     return false;
242                 }
243             }
244         }
245         return true;
246     }
247 
initiateInstall()248     private void initiateInstall() {
249         String pkgName = mPkgInfo.packageName;
250         // Check if there is already a package on the device with this name
251         // but it has been renamed to something else.
252         String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
253         if (oldName != null && oldName.length > 0 && oldName[0] != null) {
254             pkgName = oldName[0];
255             mPkgInfo.packageName = pkgName;
256             mPkgInfo.applicationInfo.packageName = pkgName;
257         }
258         // Check if package is already installed. display confirmation dialog if replacing pkg
259         try {
260             // This is a little convoluted because we want to get all uninstalled
261             // apps, but this may include apps with just data, and if it is just
262             // data we still want to count it as "installed".
263             mAppInfo = mPm.getApplicationInfo(pkgName,
264                     PackageManager.MATCH_UNINSTALLED_PACKAGES);
265             if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
266                 mAppInfo = null;
267             }
268         } catch (NameNotFoundException e) {
269             mAppInfo = null;
270         }
271 
272         startInstallConfirm();
273     }
274 
setPmResult(int pmResult)275     void setPmResult(int pmResult) {
276         Intent result = new Intent();
277         result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult);
278         setResult(pmResult == PackageManager.INSTALL_SUCCEEDED
279                 ? RESULT_OK : RESULT_FIRST_USER, result);
280     }
281 
282     @Override
onCreate(Bundle icicle)283     protected void onCreate(Bundle icicle) {
284         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
285 
286         super.onCreate(null);
287 
288         if (icicle != null) {
289             mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
290         }
291 
292         mPm = getPackageManager();
293         mIpm = AppGlobals.getPackageManager();
294         mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
295         mInstaller = mPm.getPackageInstaller();
296         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
297 
298         final Intent intent = getIntent();
299 
300         mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
301         mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
302         mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
303                 PackageInstaller.SessionParams.UID_UNKNOWN);
304         mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
305                 ? getPackageNameForUid(mOriginatingUid) : null;
306 
307 
308         final Uri packageUri;
309 
310         if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
311             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
312             final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
313             if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
314                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
315                 finish();
316                 return;
317             }
318 
319             mSessionId = sessionId;
320             packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
321             mOriginatingURI = null;
322             mReferrerURI = null;
323         } else {
324             mSessionId = -1;
325             packageUri = intent.getData();
326             mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
327             mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
328         }
329 
330         // if there's nothing to do, quietly slip into the ether
331         if (packageUri == null) {
332             Log.w(TAG, "Unspecified source");
333             setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
334             finish();
335             return;
336         }
337 
338         if (DeviceUtils.isWear(this)) {
339             showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
340             return;
341         }
342 
343         boolean wasSetUp = processPackageUri(packageUri);
344         if (!wasSetUp) {
345             return;
346         }
347 
348         // load dummy layout with OK button disabled until we override this layout in
349         // startInstallConfirm
350         bindUi();
351         checkIfAllowedAndInitiateInstall();
352     }
353 
354     @Override
onResume()355     protected void onResume() {
356         super.onResume();
357 
358         if (mOk != null) {
359             mOk.setEnabled(mEnableOk);
360         }
361     }
362 
363     @Override
onPause()364     protected void onPause() {
365         super.onPause();
366 
367         if (mOk != null) {
368             // Don't allow the install button to be clicked as there might be overlays
369             mOk.setEnabled(false);
370         }
371     }
372 
373     @Override
onSaveInstanceState(Bundle outState)374     protected void onSaveInstanceState(Bundle outState) {
375         super.onSaveInstanceState(outState);
376 
377         outState.putBoolean(ALLOW_UNKNOWN_SOURCES_KEY, mAllowUnknownSources);
378     }
379 
bindUi()380     private void bindUi() {
381         mAlert.setIcon(mAppSnippet.icon);
382         mAlert.setTitle(mAppSnippet.label);
383         mAlert.setView(R.layout.install_content_view);
384         mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
385                 (ignored, ignored2) -> {
386                     if (mOk.isEnabled()) {
387                         if (mSessionId != -1) {
388                             mInstaller.setPermissionsResult(mSessionId, true);
389                             finish();
390                         } else {
391                             startInstall();
392                         }
393                     }
394                 }, null);
395         mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
396                 (ignored, ignored2) -> {
397                     // Cancel and finish
398                     setResult(RESULT_CANCELED);
399                     if (mSessionId != -1) {
400                         mInstaller.setPermissionsResult(mSessionId, false);
401                     }
402                     finish();
403                 }, null);
404         setupAlert();
405 
406         mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
407         mOk.setEnabled(false);
408     }
409 
410     /**
411      * Check if it is allowed to install the package and initiate install if allowed. If not allowed
412      * show the appropriate dialog.
413      */
checkIfAllowedAndInitiateInstall()414     private void checkIfAllowedAndInitiateInstall() {
415         // Check for install apps user restriction first.
416         final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
417                 UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
418         if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
419             showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
420             return;
421         } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
422             startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
423             finish();
424             return;
425         }
426 
427         if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
428             initiateInstall();
429         } else {
430             // Check for unknown sources restrictions.
431             final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
432                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
433             final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
434                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
435             final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
436                     & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
437             if (systemRestriction != 0) {
438                 showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
439             } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
440                 startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
441             } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
442                 startAdminSupportDetailsActivity(
443                         UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
444             } else {
445                 handleUnknownSources();
446             }
447         }
448     }
449 
startAdminSupportDetailsActivity(String restriction)450     private void startAdminSupportDetailsActivity(String restriction) {
451         // If the given restriction is set by an admin, display information about the
452         // admin enforcing the restriction for the affected user.
453         final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
454         final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
455         if (showAdminSupportDetailsIntent != null) {
456             startActivity(showAdminSupportDetailsIntent);
457         }
458         finish();
459     }
460 
handleUnknownSources()461     private void handleUnknownSources() {
462         if (mOriginatingPackage == null) {
463             Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
464             showDialogInner(DLG_ANONYMOUS_SOURCE);
465             return;
466         }
467         // Shouldn't use static constant directly, see b/65534401.
468         final int appOpCode =
469                 AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
470         final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode,
471                 mOriginatingUid, mOriginatingPackage);
472         switch (appOpMode) {
473             case AppOpsManager.MODE_DEFAULT:
474                 mAppOpsManager.setMode(appOpCode, mOriginatingUid,
475                         mOriginatingPackage, AppOpsManager.MODE_ERRORED);
476                 // fall through
477             case AppOpsManager.MODE_ERRORED:
478                 showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
479                 break;
480             case AppOpsManager.MODE_ALLOWED:
481                 initiateInstall();
482                 break;
483             default:
484                 Log.e(TAG, "Invalid app op mode " + appOpMode
485                         + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
486                 finish();
487                 break;
488         }
489     }
490 
491     /**
492      * Parse the Uri and set up the installer for this package.
493      *
494      * @param packageUri The URI to parse
495      *
496      * @return {@code true} iff the installer could be set up
497      */
processPackageUri(final Uri packageUri)498     private boolean processPackageUri(final Uri packageUri) {
499         mPackageURI = packageUri;
500 
501         final String scheme = packageUri.getScheme();
502 
503         switch (scheme) {
504             case SCHEME_PACKAGE: {
505                 try {
506                     mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
507                             PackageManager.GET_PERMISSIONS
508                                     | PackageManager.MATCH_UNINSTALLED_PACKAGES);
509                 } catch (NameNotFoundException e) {
510                 }
511                 if (mPkgInfo == null) {
512                     Log.w(TAG, "Requested package " + packageUri.getScheme()
513                             + " not available. Discontinuing installation");
514                     showDialogInner(DLG_PACKAGE_ERROR);
515                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
516                     return false;
517                 }
518                 mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
519                         mPm.getApplicationIcon(mPkgInfo.applicationInfo));
520             } break;
521 
522             case ContentResolver.SCHEME_FILE: {
523                 File sourceFile = new File(packageUri.getPath());
524                 PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
525 
526                 // Check for parse errors
527                 if (parsed == null) {
528                     Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
529                     showDialogInner(DLG_PACKAGE_ERROR);
530                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
531                     return false;
532                 }
533                 mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
534                         PackageManager.GET_PERMISSIONS, 0, 0, null,
535                         new PackageUserState());
536                 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
537             } break;
538 
539             default: {
540                 throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
541             }
542         }
543 
544         return true;
545     }
546 
547     @Override
onBackPressed()548     public void onBackPressed() {
549         if (mSessionId != -1) {
550             mInstaller.setPermissionsResult(mSessionId, false);
551         }
552         super.onBackPressed();
553     }
554 
startInstall()555     private void startInstall() {
556         // Start subactivity to actually install the application
557         Intent newIntent = new Intent();
558         newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
559                 mPkgInfo.applicationInfo);
560         newIntent.setData(mPackageURI);
561         newIntent.setClass(this, InstallInstalling.class);
562         String installerPackageName = getIntent().getStringExtra(
563                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
564         if (mOriginatingURI != null) {
565             newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
566         }
567         if (mReferrerURI != null) {
568             newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
569         }
570         if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
571             newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
572         }
573         if (installerPackageName != null) {
574             newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
575                     installerPackageName);
576         }
577         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
578             newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
579         }
580         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
581         if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
582         startActivity(newIntent);
583         finish();
584     }
585 
586     /**
587      * A simple error dialog showing a message
588      */
589     public static class SimpleErrorDialog extends DialogFragment {
590         private static final String MESSAGE_KEY =
591                 SimpleErrorDialog.class.getName() + "MESSAGE_KEY";
592 
newInstance(@tringRes int message)593         static SimpleErrorDialog newInstance(@StringRes int message) {
594             SimpleErrorDialog dialog = new SimpleErrorDialog();
595 
596             Bundle args = new Bundle();
597             args.putInt(MESSAGE_KEY, message);
598             dialog.setArguments(args);
599 
600             return dialog;
601         }
602 
603         @Override
onCreateDialog(Bundle savedInstanceState)604         public Dialog onCreateDialog(Bundle savedInstanceState) {
605             return new AlertDialog.Builder(getActivity())
606                     .setMessage(getArguments().getInt(MESSAGE_KEY))
607                     .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
608                     .create();
609         }
610     }
611 
612     /**
613      * Dialog to show when the source of apk can not be identified
614      */
615     public static class AnonymousSourceDialog extends DialogFragment {
newInstance()616         static AnonymousSourceDialog newInstance() {
617             return new AnonymousSourceDialog();
618         }
619 
620         @Override
onCreateDialog(Bundle savedInstanceState)621         public Dialog onCreateDialog(Bundle savedInstanceState) {
622             return new AlertDialog.Builder(getActivity())
623                     .setMessage(R.string.anonymous_source_warning)
624                     .setPositiveButton(R.string.anonymous_source_continue,
625                             ((dialog, which) -> {
626                                 PackageInstallerActivity activity = ((PackageInstallerActivity)
627                                         getActivity());
628 
629                                 activity.mAllowUnknownSources = true;
630                                 activity.initiateInstall();
631                             }))
632                     .setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish()))
633                     .create();
634         }
635 
636         @Override
onCancel(DialogInterface dialog)637         public void onCancel(DialogInterface dialog) {
638             getActivity().finish();
639         }
640     }
641 
642     /**
643      * An error dialog shown when the app is not supported on wear
644      */
645     public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
newInstance()646         static SimpleErrorDialog newInstance() {
647             return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
648         }
649 
650         @Override
onCancel(DialogInterface dialog)651         public void onCancel(DialogInterface dialog) {
652             getActivity().setResult(RESULT_OK);
653             getActivity().finish();
654         }
655     }
656 
657     /**
658      * An error dialog shown when the device is out of space
659      */
660     public static class OutOfSpaceDialog extends AppErrorDialog {
newInstance(@onNull CharSequence applicationLabel)661         static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
662             OutOfSpaceDialog dialog = new OutOfSpaceDialog();
663             dialog.setArgument(applicationLabel);
664             return dialog;
665         }
666 
667         @Override
createDialog(@onNull CharSequence argument)668         protected Dialog createDialog(@NonNull CharSequence argument) {
669             String dlgText = getString(R.string.out_of_space_dlg_text, argument);
670             return new AlertDialog.Builder(getActivity())
671                     .setMessage(dlgText)
672                     .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
673                         // launch manage applications
674                         Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
675                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
676                         startActivity(intent);
677                         getActivity().finish();
678                     })
679                     .setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish())
680                     .create();
681         }
682     }
683 
684     /**
685      * A generic install-error dialog
686      */
687     public static class InstallErrorDialog extends AppErrorDialog {
688         static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
689             InstallErrorDialog dialog = new InstallErrorDialog();
690             dialog.setArgument(applicationLabel);
691             return dialog;
692         }
693 
694         @Override
695         protected Dialog createDialog(@NonNull CharSequence argument) {
696             return new AlertDialog.Builder(getActivity())
697                     .setNeutralButton(R.string.ok, (dialog, which) -> getActivity().finish())
698                     .setMessage(getString(R.string.install_failed_msg, argument))
699                     .create();
700         }
701     }
702 
703     /**
704      * An error dialog shown when external sources are not allowed
705      */
706     public static class ExternalSourcesBlockedDialog extends AppErrorDialog {
707         static AppErrorDialog newInstance(@NonNull String originationPkg) {
708             ExternalSourcesBlockedDialog dialog = new ExternalSourcesBlockedDialog();
709             dialog.setArgument(originationPkg);
710             return dialog;
711         }
712 
713         @Override
714         protected Dialog createDialog(@NonNull CharSequence argument) {
715             try {
716                 PackageManager pm = getActivity().getPackageManager();
717 
718                 ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0);
719 
720                 return new AlertDialog.Builder(getActivity())
721                         .setTitle(pm.getApplicationLabel(sourceInfo))
722                         .setIcon(pm.getApplicationIcon(sourceInfo))
723                         .setMessage(R.string.untrusted_external_source_warning)
724                         .setPositiveButton(R.string.external_sources_settings,
725                                 (dialog, which) -> {
726                                     Intent settingsIntent = new Intent();
727                                     settingsIntent.setAction(
728                                             Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
729                                     final Uri packageUri = Uri.parse("package:" + argument);
730                                     settingsIntent.setData(packageUri);
731                                     try {
732                                         getActivity().startActivityForResult(settingsIntent,
733                                                 REQUEST_TRUST_EXTERNAL_SOURCE);
734                                     } catch (ActivityNotFoundException exc) {
735                                         Log.e(TAG, "Settings activity not found for action: "
736                                                 + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
737                                     }
738                                 })
739                         .setNegativeButton(R.string.cancel,
740                                 (dialog, which) -> getActivity().finish())
741                         .create();
742             } catch (NameNotFoundException e) {
743                 Log.e(TAG, "Did not find app info for " + argument);
744                 getActivity().finish();
745                 return null;
746             }
747         }
748     }
749 
750     /**
751      * Superclass for all error dialogs. Stores a single CharSequence argument
752      */
753     public abstract static class AppErrorDialog extends DialogFragment {
754         private static final String ARGUMENT_KEY = AppErrorDialog.class.getName() + "ARGUMENT_KEY";
755 
756         protected void setArgument(@NonNull CharSequence argument) {
757             Bundle args = new Bundle();
758             args.putCharSequence(ARGUMENT_KEY, argument);
759             setArguments(args);
760         }
761 
762         protected abstract Dialog createDialog(@NonNull CharSequence argument);
763 
764         @Override
765         public Dialog onCreateDialog(Bundle savedInstanceState) {
766             return createDialog(getArguments().getString(ARGUMENT_KEY));
767         }
768 
769         @Override
770         public void onCancel(DialogInterface dialog) {
771             getActivity().finish();
772         }
773     }
774 }
775