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