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