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