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