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