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         // load dummy layout with OK button disabled until we override this layout in
348         // startInstallConfirm
349         bindUi();
350         checkIfAllowedAndInitiateInstall();
351     }
352 
353     @Override
onResume()354     protected void onResume() {
355         super.onResume();
356 
357         if (mOk != null) {
358             mOk.setEnabled(mEnableOk);
359         }
360     }
361 
362     @Override
onPause()363     protected void onPause() {
364         super.onPause();
365 
366         if (mOk != null) {
367             // Don't allow the install button to be clicked as there might be overlays
368             mOk.setEnabled(false);
369         }
370     }
371 
372     @Override
onSaveInstanceState(Bundle outState)373     protected void onSaveInstanceState(Bundle outState) {
374         super.onSaveInstanceState(outState);
375 
376         outState.putBoolean(ALLOW_UNKNOWN_SOURCES_KEY, mAllowUnknownSources);
377     }
378 
bindUi()379     private void bindUi() {
380         mAlert.setIcon(mAppSnippet.icon);
381         mAlert.setTitle(mAppSnippet.label);
382         mAlert.setView(R.layout.install_content_view);
383         mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
384                 (ignored, ignored2) -> {
385                     if (mOk.isEnabled()) {
386                         if (mSessionId != -1) {
387                             mInstaller.setPermissionsResult(mSessionId, true);
388                             finish();
389                         } else {
390                             startInstall();
391                         }
392                     }
393                 }, null);
394         mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
395                 (ignored, ignored2) -> {
396                     // Cancel and finish
397                     setResult(RESULT_CANCELED);
398                     if (mSessionId != -1) {
399                         mInstaller.setPermissionsResult(mSessionId, false);
400                     }
401                     finish();
402                 }, null);
403         setupAlert();
404 
405         mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
406         mOk.setEnabled(false);
407 
408         if (!mOk.isInTouchMode()) {
409             mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
410         }
411     }
412 
413     /**
414      * Check if it is allowed to install the package and initiate install if allowed. If not allowed
415      * show the appropriate dialog.
416      */
checkIfAllowedAndInitiateInstall()417     private void checkIfAllowedAndInitiateInstall() {
418         // Check for install apps user restriction first.
419         final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
420                 UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
421         if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
422             showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
423             return;
424         } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
425             startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
426             finish();
427             return;
428         }
429 
430         if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
431             initiateInstall();
432         } else {
433             // Check for unknown sources restrictions.
434             final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
435                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
436             final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
437                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
438             final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
439                     & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
440             if (systemRestriction != 0) {
441                 showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
442             } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
443                 startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
444             } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
445                 startAdminSupportDetailsActivity(
446                         UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
447             } else {
448                 handleUnknownSources();
449             }
450         }
451     }
452 
startAdminSupportDetailsActivity(String restriction)453     private void startAdminSupportDetailsActivity(String restriction) {
454         // If the given restriction is set by an admin, display information about the
455         // admin enforcing the restriction for the affected user.
456         final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
457         final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
458         if (showAdminSupportDetailsIntent != null) {
459             startActivity(showAdminSupportDetailsIntent);
460         }
461         finish();
462     }
463 
handleUnknownSources()464     private void handleUnknownSources() {
465         if (mOriginatingPackage == null) {
466             Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
467             showDialogInner(DLG_ANONYMOUS_SOURCE);
468             return;
469         }
470         // Shouldn't use static constant directly, see b/65534401.
471         final int appOpCode =
472                 AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
473         final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode,
474                 mOriginatingUid, mOriginatingPackage);
475         switch (appOpMode) {
476             case AppOpsManager.MODE_DEFAULT:
477                 mAppOpsManager.setMode(appOpCode, mOriginatingUid,
478                         mOriginatingPackage, AppOpsManager.MODE_ERRORED);
479                 // fall through
480             case AppOpsManager.MODE_ERRORED:
481                 showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
482                 break;
483             case AppOpsManager.MODE_ALLOWED:
484                 initiateInstall();
485                 break;
486             default:
487                 Log.e(TAG, "Invalid app op mode " + appOpMode
488                         + " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
489                 finish();
490                 break;
491         }
492     }
493 
494     /**
495      * Parse the Uri and set up the installer for this package.
496      *
497      * @param packageUri The URI to parse
498      *
499      * @return {@code true} iff the installer could be set up
500      */
processPackageUri(final Uri packageUri)501     private boolean processPackageUri(final Uri packageUri) {
502         mPackageURI = packageUri;
503 
504         final String scheme = packageUri.getScheme();
505 
506         switch (scheme) {
507             case SCHEME_PACKAGE: {
508                 try {
509                     mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
510                             PackageManager.GET_PERMISSIONS
511                                     | PackageManager.MATCH_UNINSTALLED_PACKAGES);
512                 } catch (NameNotFoundException e) {
513                 }
514                 if (mPkgInfo == null) {
515                     Log.w(TAG, "Requested package " + packageUri.getScheme()
516                             + " not available. Discontinuing installation");
517                     showDialogInner(DLG_PACKAGE_ERROR);
518                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
519                     return false;
520                 }
521                 mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
522                         mPm.getApplicationIcon(mPkgInfo.applicationInfo));
523             } break;
524 
525             case ContentResolver.SCHEME_FILE: {
526                 File sourceFile = new File(packageUri.getPath());
527                 mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
528                         PackageManager.GET_PERMISSIONS);
529 
530                 // Check for parse errors
531                 if (mPkgInfo == null) {
532                     Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
533                     showDialogInner(DLG_PACKAGE_ERROR);
534                     setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
535                     return false;
536                 }
537                 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
538             } break;
539 
540             default: {
541                 throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
542             }
543         }
544 
545         return true;
546     }
547 
548     @Override
onBackPressed()549     public void onBackPressed() {
550         if (mSessionId != -1) {
551             mInstaller.setPermissionsResult(mSessionId, false);
552         }
553         super.onBackPressed();
554     }
555 
startInstall()556     private void startInstall() {
557         // Start subactivity to actually install the application
558         Intent newIntent = new Intent();
559         newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
560                 mPkgInfo.applicationInfo);
561         newIntent.setData(mPackageURI);
562         newIntent.setClass(this, InstallInstalling.class);
563         String installerPackageName = getIntent().getStringExtra(
564                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
565         if (mOriginatingURI != null) {
566             newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
567         }
568         if (mReferrerURI != null) {
569             newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
570         }
571         if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
572             newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
573         }
574         if (installerPackageName != null) {
575             newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
576                     installerPackageName);
577         }
578         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
579             newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
580         }
581         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
582         if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
583         startActivity(newIntent);
584         finish();
585     }
586 
587     /**
588      * A simple error dialog showing a message
589      */
590     public static class SimpleErrorDialog extends DialogFragment {
591         private static final String MESSAGE_KEY =
592                 SimpleErrorDialog.class.getName() + "MESSAGE_KEY";
593 
newInstance(@tringRes int message)594         static SimpleErrorDialog newInstance(@StringRes int message) {
595             SimpleErrorDialog dialog = new SimpleErrorDialog();
596 
597             Bundle args = new Bundle();
598             args.putInt(MESSAGE_KEY, message);
599             dialog.setArguments(args);
600 
601             return dialog;
602         }
603 
604         @Override
onCreateDialog(Bundle savedInstanceState)605         public Dialog onCreateDialog(Bundle savedInstanceState) {
606             return new AlertDialog.Builder(getActivity())
607                     .setMessage(getArguments().getInt(MESSAGE_KEY))
608                     .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
609                     .create();
610         }
611     }
612 
613     /**
614      * Dialog to show when the source of apk can not be identified
615      */
616     public static class AnonymousSourceDialog extends DialogFragment {
newInstance()617         static AnonymousSourceDialog newInstance() {
618             return new AnonymousSourceDialog();
619         }
620 
621         @Override
onCreateDialog(Bundle savedInstanceState)622         public Dialog onCreateDialog(Bundle savedInstanceState) {
623             return new AlertDialog.Builder(getActivity())
624                     .setMessage(R.string.anonymous_source_warning)
625                     .setPositiveButton(R.string.anonymous_source_continue,
626                             ((dialog, which) -> {
627                                 PackageInstallerActivity activity = ((PackageInstallerActivity)
628                                         getActivity());
629 
630                                 activity.mAllowUnknownSources = true;
631                                 activity.initiateInstall();
632                             }))
633                     .setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish()))
634                     .create();
635         }
636 
637         @Override
onCancel(DialogInterface dialog)638         public void onCancel(DialogInterface dialog) {
639             getActivity().finish();
640         }
641     }
642 
643     /**
644      * An error dialog shown when the app is not supported on wear
645      */
646     public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
newInstance()647         static SimpleErrorDialog newInstance() {
648             return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
649         }
650 
651         @Override
onCancel(DialogInterface dialog)652         public void onCancel(DialogInterface dialog) {
653             getActivity().setResult(RESULT_OK);
654             getActivity().finish();
655         }
656     }
657 
658     /**
659      * An error dialog shown when the device is out of space
660      */
661     public static class OutOfSpaceDialog extends AppErrorDialog {
newInstance(@onNull CharSequence applicationLabel)662         static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
663             OutOfSpaceDialog dialog = new OutOfSpaceDialog();
664             dialog.setArgument(applicationLabel);
665             return dialog;
666         }
667 
668         @Override
createDialog(@onNull CharSequence argument)669         protected Dialog createDialog(@NonNull CharSequence argument) {
670             String dlgText = getString(R.string.out_of_space_dlg_text, argument);
671             return new AlertDialog.Builder(getActivity())
672                     .setMessage(dlgText)
673                     .setPositiveButton(R.string.manage_applications, (dialog, which) -> {
674                         // launch manage applications
675                         Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
676                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
677                         startActivity(intent);
678                         getActivity().finish();
679                     })
680                     .setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish())
681                     .create();
682         }
683     }
684 
685     /**
686      * A generic install-error dialog
687      */
688     public static class InstallErrorDialog extends AppErrorDialog {
689         static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
690             InstallErrorDialog dialog = new InstallErrorDialog();
691             dialog.setArgument(applicationLabel);
692             return dialog;
693         }
694 
695         @Override
696         protected Dialog createDialog(@NonNull CharSequence argument) {
697             return new AlertDialog.Builder(getActivity())
698                     .setNeutralButton(R.string.ok, (dialog, which) -> getActivity().finish())
699                     .setMessage(getString(R.string.install_failed_msg, argument))
700                     .create();
701         }
702     }
703 
704     /**
705      * An error dialog shown when external sources are not allowed
706      */
707     public static class ExternalSourcesBlockedDialog extends AppErrorDialog {
708         static AppErrorDialog newInstance(@NonNull String originationPkg) {
709             ExternalSourcesBlockedDialog dialog = new ExternalSourcesBlockedDialog();
710             dialog.setArgument(originationPkg);
711             return dialog;
712         }
713 
714         @Override
715         protected Dialog createDialog(@NonNull CharSequence argument) {
716             try {
717                 PackageManager pm = getActivity().getPackageManager();
718 
719                 ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0);
720 
721                 return new AlertDialog.Builder(getActivity())
722                         .setTitle(pm.getApplicationLabel(sourceInfo))
723                         .setIcon(pm.getApplicationIcon(sourceInfo))
724                         .setMessage(R.string.untrusted_external_source_warning)
725                         .setPositiveButton(R.string.external_sources_settings,
726                                 (dialog, which) -> {
727                                     Intent settingsIntent = new Intent();
728                                     settingsIntent.setAction(
729                                             Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
730                                     final Uri packageUri = Uri.parse("package:" + argument);
731                                     settingsIntent.setData(packageUri);
732                                     try {
733                                         getActivity().startActivityForResult(settingsIntent,
734                                                 REQUEST_TRUST_EXTERNAL_SOURCE);
735                                     } catch (ActivityNotFoundException exc) {
736                                         Log.e(TAG, "Settings activity not found for action: "
737                                                 + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
738                                     }
739                                 })
740                         .setNegativeButton(R.string.cancel,
741                                 (dialog, which) -> getActivity().finish())
742                         .create();
743             } catch (NameNotFoundException e) {
744                 Log.e(TAG, "Did not find app info for " + argument);
745                 getActivity().finish();
746                 return null;
747             }
748         }
749     }
750 
751     /**
752      * Superclass for all error dialogs. Stores a single CharSequence argument
753      */
754     public abstract static class AppErrorDialog extends DialogFragment {
755         private static final String ARGUMENT_KEY = AppErrorDialog.class.getName() + "ARGUMENT_KEY";
756 
757         protected void setArgument(@NonNull CharSequence argument) {
758             Bundle args = new Bundle();
759             args.putCharSequence(ARGUMENT_KEY, argument);
760             setArguments(args);
761         }
762 
763         protected abstract Dialog createDialog(@NonNull CharSequence argument);
764 
765         @Override
766         public Dialog onCreateDialog(Bundle savedInstanceState) {
767             return createDialog(getArguments().getString(ARGUMENT_KEY));
768         }
769 
770         @Override
771         public void onCancel(DialogInterface dialog) {
772             getActivity().finish();
773         }
774     }
775 }
776