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