packages/apps/PackageInstaller/AndroidManifest.xml
<activity android:name=".InstallStart"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
package com.android.packageinstaller;
import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/**
* Select which activity is the first visible activity of the installation and forward the intent to
* it.
*/
public class InstallStart extends Activity {
private static final String LOG_TAG = InstallStart.class.getSimpleName();
private static final String DOWNLOADS_AUTHORITY = "downloads";
private IActivityManager mIActivityManager;
private IPackageManager mIPackageManager;
private boolean mAbortInstall = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIPackageManager = AppGlobals.getPackageManager();
Intent intent = getIntent();
String callingPackage = getCallingPackage();
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
private boolean declaresAppOpPermission(int uid, String permission) {
try {
final String[] packages = mIPackageManager.getAppOpPermissionPackages(permission);
if (packages == null) {
return false;
}
for (String packageName : packages) {
try {
if (uid == getPackageManager().getPackageUid(packageName, 0)) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
// Ignore and try the next package
}
}
} catch (RemoteException rexc) {
// If remote package manager cannot be reached, install will likely fail anyway.
}
return false;
}
/**
* @return the ApplicationInfo for the installation source (the calling package), if available
*/
private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
if (callingPackage != null) {
try {
return getPackageManager().getApplicationInfo(callingPackage, 0);
} catch (PackageManager.NameNotFoundException ex) {
// ignore
}
}
return null;
}
/**
* Get the originating uid if possible, or
* {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
*
* @param sourceInfo The source of this installation
* @return The UID of the installation source or UID_UNKNOWN
*/
private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
// The originating uid from the intent. We only trust/use this if it comes from either
// the document manager app or the downloads provider
final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
final int callingUid;
if (sourceInfo != null) {
callingUid = sourceInfo.uid;
} else {
try {
callingUid = getIActivityManager()
.getLaunchedFromUid(getActivityToken());
} catch (RemoteException ex) {
// Cannot reach ActivityManager. Aborting install.
Log.e(LOG_TAG, "Could not determine the launching uid.");
mAbortInstall = true;
return PackageInstaller.SessionParams.UID_UNKNOWN;
}
}
try {
if (mIPackageManager.checkUidPermission(Manifest.permission.MANAGE_DOCUMENTS,
callingUid) == PackageManager.PERMISSION_GRANTED) {
return uidFromIntent;
}
} catch (RemoteException rexc) {
// Ignore. Should not happen.
}
if (isSystemDownloadsProvider(callingUid)) {
return uidFromIntent;
}
// We don't trust uid from the intent. Use the calling uid instead.
return callingUid;
}
private boolean isSystemDownloadsProvider(int uid) {
final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
DOWNLOADS_AUTHORITY, 0);
if (downloadProviderPackage == null) {
// There seems to be no currently enabled downloads provider on the system.
return false;
}
final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
return (appInfo.isSystemApp() && uid == appInfo.uid);
}
private IActivityManager getIActivityManager() {
if (mIActivityManager == null) {
return ActivityManager.getService();
}
return mIActivityManager;
}
@VisibleForTesting
void injectIActivityManager(IActivityManager iActivityManager) {
mIActivityManager = iActivityManager;
}
}
packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
package com.android.packageinstaller;
import android.Manifest;
import android.app.AlertDialog;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AppSecurityPermissions;
import android.widget.Button;
import android.widget.TabHost;
import android.widget.TextView;
import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
import java.io.File;
/**
* This activity is launched when a new application is installed via side loading
* The package is first parsed and the user is notified of parse errors via a dialog.
* If the package is successfully parsed, the user is notified to turn on the install unknown
* applications setting. A memory check is made at this point and the user is notified of out
* of memory conditions if any. If the package is already existing on the device,
* a confirmation dialog (to replace the existing package) is presented to the user.
* Based on the user response the package is then installed by launching InstallAppConfirm
* sub activity. All state transitions are handled in this activity
*/
public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
private static final String TAG = "PackageInstaller";
private static final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
static final String SCHEME_PACKAGE = "package";
static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE";
static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
private static final String ALLOW_UNKNOWN_SOURCES_KEY =
PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
private int mSessionId = -1;
private Uri mPackageURI;
private Uri mOriginatingURI;
private Uri mReferrerURI;
private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
private boolean localLOGV = false;
PackageManager mPm;
IPackageManager mIpm;
AppOpsManager mAppOpsManager;
UserManager mUserManager;
PackageInstaller mInstaller;
PackageInfo mPkgInfo;
String mCallingPackage;
ApplicationInfo mSourceInfo;
// ApplicationInfo object primarily used for already existing applications
private ApplicationInfo mAppInfo = null;
// Buttons to indicate user acceptance
private Button mOk;
private Button mCancel;
CaffeinatedScrollView mScrollView = null;
private boolean mOkCanInstall = false;
private PackageUtil.AppSnippet mAppSnippet;
static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
private static final String TAB_ID_ALL = "all";
private static final String TAB_ID_NEW = "new";
// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
// If unknown sources are temporary allowed
private boolean mAllowUnknownSources;
// Would the mOk button be enabled if this activity would be resumed
private boolean mEnableOk;
private void startInstallConfirm() {
// We might need to show permissions, load layout with permissions
if (mAppInfo != null) {
bindUi(R.layout.install_confirm_perm_update, true);
} else {
bindUi(R.layout.install_confirm_perm, true);
}
((TextView) findViewById(R.id.install_confirm_question))
.setText(R.string.install_confirm_question);
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
// If the app supports runtime permissions the new permissions will
// be requested at runtime, hence we do not show them at install.
boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
boolean permVisible = false;
mScrollView = null;
mOkCanInstall = false;
int msg = 0;
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
if (mAppInfo != null) {
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system
: R.string.install_confirm_question_update;
mScrollView = new CaffeinatedScrollView(this);
mScrollView.setFillViewport(true);
boolean newPermissionsFound = false;
if (!supportsRuntimePermissions) {
newPermissionsFound =
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
if (newPermissionsFound) {
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
}
}
if (!supportsRuntimePermissions && !newPermissionsFound) {
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
TextView label = (TextView)inflater.inflate(R.layout.label, null);
label.setText(R.string.no_new_perms);
mScrollView.addView(label);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
}
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View root = inflater.inflate(R.layout.permissions_list, null);
if (mScrollView == null) {
mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
}
((ViewGroup)root.findViewById(R.id.permission_list)).addView(
perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
if (!permVisible) {
if (mAppInfo != null) {
// This is an update to an application, but there are no
// permissions at all.
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
} else {
// This is a new application with no permissions.
msg = R.string.install_confirm_question_no_perms;
}
// We do not need to show any permissions, load layout without permissions
bindUi(R.layout.install_confirm, true);
mScrollView = null;
}
if (msg != 0) {
((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
}
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
mOk.setText(R.string.install);
mOkCanInstall = true;
} else {
mScrollView.setFullScrollAction(new Runnable() {
@Override
public void run() {
mOk.setText(R.string.install);
mOkCanInstall = true;
}
});
}
}
/**
* Replace any dialog shown by the dialog with the one for the given {@link #createDialog id}.
*
* @param id The dialog type to add
*/
private void showDialogInner(int id) {
DialogFragment currentDialog =
(DialogFragment) getFragmentManager().findFragmentByTag("dialog");
if (currentDialog != null) {
currentDialog.dismissAllowingStateLoss();
}
DialogFragment newDialog = createDialog(id);
if (newDialog != null) {
newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
}
}
/**
* Create a new dialog.
*
* @param id The id of the dialog (determines dialog type)
*
* @return The dialog
*/
private DialogFragment createDialog(int id) {
switch (id) {
case DLG_PACKAGE_ERROR:
return SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
case DLG_OUT_OF_SPACE:
return OutOfSpaceDialog.newInstance(
mPm.getApplicationLabel(mPkgInfo.applicationInfo));
case DLG_INSTALL_ERROR:
return InstallErrorDialog.newInstance(
mPm.getApplicationLabel(mPkgInfo.applicationInfo));
case DLG_NOT_SUPPORTED_ON_WEAR:
return NotSupportedOnWearDialog.newInstance();
case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
return SimpleErrorDialog.newInstance(
R.string.install_apps_user_restriction_dlg_text);
case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
return SimpleErrorDialog.newInstance(
R.string.unknown_apps_user_restriction_dlg_text);
case DLG_EXTERNAL_SOURCE_BLOCKED:
return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage);
case DLG_ANONYMOUS_SOURCE:
return AnonymousSourceDialog.newInstance();
}
return null;
}
@Override
public void onActivityResult(int request, int result, Intent data) {
if (request == REQUEST_TRUST_EXTERNAL_SOURCE && result == RESULT_OK) {
// The user has just allowed this package to install other packages (via Settings).
mAllowUnknownSources = true;
// Log the fact that the app is requesting an install, and is now allowed to do it
// (before this point we could only log that it's requesting an install, but isn't
// allowed to do it yet).
int appOpCode =
AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage);
DialogFragment currentDialog =
(DialogFragment) getFragmentManager().findFragmentByTag("dialog");
if (currentDialog != null) {
currentDialog.dismissAllowingStateLoss();
}
initiateInstall();
} else {
finish();
}
}
private String getPackageNameForUid(int sourceUid) {
String[] packagesForUid = mPm.getPackagesForUid(sourceUid);
if (packagesForUid == null) {
return null;
}
if (packagesForUid.length > 1) {
if (mCallingPackage != null) {
for (String packageName : packagesForUid) {
if (packageName.equals(mCallingPackage)) {
return packageName;
}
}
}
Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
}
return packagesForUid[0];
}
private boolean isInstallRequestFromUnknownSource(Intent intent) {
if (mCallingPackage != null && intent.getBooleanExtra(
Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
if (mSourceInfo != null) {
if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
!= 0) {
// Privileged apps can bypass unknown sources check if they want.
return false;
}
}
}
return true;
}
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
void setPmResult(int pmResult) {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, pmResult);
setResult(pmResult == PackageManager.INSTALL_SUCCEEDED
? RESULT_OK : RESULT_FIRST_USER, result);
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(null);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi(R.layout.install_confirm, false);
checkIfAllowedAndInitiateInstall();
}
@Override
protected void onResume() {
super.onResume();
if (mOk != null) {
mOk.setEnabled(mEnableOk);
}
}
@Override
protected void onPause() {
super.onPause();
if (mOk != null) {
// Don't allow the install button to be clicked as there might be overlays
mOk.setEnabled(false);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(ALLOW_UNKNOWN_SOURCES_KEY, mAllowUnknownSources);
}
private void bindUi(int layout, boolean enableOk) {
setContentView(layout);
mOk = (Button) findViewById(R.id.ok_button);
mCancel = (Button)findViewById(R.id.cancel_button);
mOk.setOnClickListener(this);
mCancel.setOnClickListener(this);
mEnableOk = enableOk;
mOk.setEnabled(enableOk);
PackageUtil.initSnippetForNewApp(this, mAppSnippet, R.id.app_snippet);
}
/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
return;
} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
return;
}
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
} else {
// Check for unknown sources restriction
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
} else {
handleUnknownSources();
}
}
}
private void handleUnknownSources() {
if (mOriginatingPackage == null) {
Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
showDialogInner(DLG_ANONYMOUS_SOURCE);
return;
}
// Shouldn't use static constant directly, see b/65534401.
final int appOpCode =
AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode,
mOriginatingUid, mOriginatingPackage);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
try {
int result = mIpm.checkUidPermission(
Manifest.permission.REQUEST_INSTALL_PACKAGES, mOriginatingUid);
if (result == PackageManager.PERMISSION_GRANTED) {
initiateInstall();
break;
}
} catch (RemoteException exc) {
Log.e(TAG, "Unable to talk to package manager");
}
mAppOpsManager.setMode(appOpCode, mOriginatingUid,
mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
case AppOpsManager.MODE_ERRORED:
showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
break;
case AppOpsManager.MODE_ALLOWED:
initiateInstall();
break;
default:
Log.e(TAG, "Invalid app op mode " + appOpMode
+ " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
finish();
break;
}
}
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
*
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
// Check for parse errors
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
@Override
public void onBackPressed() {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
super.onBackPressed();
}
public void onClick(View v) {
if (v == mOk) {
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
}
} else if (v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}
}
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
/**
* A simple error dialog showing a message
*/
public static class SimpleErrorDialog extends DialogFragment {
private static final String MESSAGE_KEY =
SimpleErrorDialog.class.getName() + "MESSAGE_KEY";
static SimpleErrorDialog newInstance(@StringRes int message) {
SimpleErrorDialog dialog = new SimpleErrorDialog();
Bundle args = new Bundle();
args.putInt(MESSAGE_KEY, message);
dialog.setArguments(args);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(getArguments().getInt(MESSAGE_KEY))
.setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
.create();
}
}
/**
* Dialog to show when the source of apk can not be identified
*/
public static class AnonymousSourceDialog extends DialogFragment {
static AnonymousSourceDialog newInstance() {
return new AnonymousSourceDialog();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.anonymous_source_warning)
.setPositiveButton(R.string.anonymous_source_continue,
((dialog, which) -> {
PackageInstallerActivity activity = ((PackageInstallerActivity)
getActivity());
activity.mAllowUnknownSources = true;
activity.initiateInstall();
}))
.setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish()))
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
getActivity().finish();
}
}
/**
* An error dialog shown when the app is not supported on wear
*/
public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
static SimpleErrorDialog newInstance() {
return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
}
@Override
public void onCancel(DialogInterface dialog) {
getActivity().setResult(RESULT_OK);
getActivity().finish();
}
}
/**
* An error dialog shown when the device is out of space
*/
public static class OutOfSpaceDialog extends AppErrorDialog {
static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
OutOfSpaceDialog dialog = new OutOfSpaceDialog();
dialog.setArgument(applicationLabel);
return dialog;
}
@Override
protected Dialog createDialog(@NonNull CharSequence argument) {
String dlgText = getString(R.string.out_of_space_dlg_text, argument);
return new AlertDialog.Builder(getActivity())
.setMessage(dlgText)
.setPositiveButton(R.string.manage_applications, (dialog, which) -> {
// launch manage applications
Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
getActivity().finish();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish())
.create();
}
}
/**
* A generic install-error dialog
*/
public static class InstallErrorDialog extends AppErrorDialog {
static AppErrorDialog newInstance(@NonNull CharSequence applicationLabel) {
InstallErrorDialog dialog = new InstallErrorDialog();
dialog.setArgument(applicationLabel);
return dialog;
}
@Override
protected Dialog createDialog(@NonNull CharSequence argument) {
return new AlertDialog.Builder(getActivity())
.setNeutralButton(R.string.ok, (dialog, which) -> getActivity().finish())
.setMessage(getString(R.string.install_failed_msg, argument))
.create();
}
}
/**
* An error dialog shown when external sources are not allowed
*/
public static class ExternalSourcesBlockedDialog extends AppErrorDialog {
static AppErrorDialog newInstance(@NonNull String originationPkg) {
ExternalSourcesBlockedDialog dialog = new ExternalSourcesBlockedDialog();
dialog.setArgument(originationPkg);
return dialog;
}
@Override
protected Dialog createDialog(@NonNull CharSequence argument) {
try {
PackageManager pm = getActivity().getPackageManager();
ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0);
return new AlertDialog.Builder(getActivity())
.setTitle(pm.getApplicationLabel(sourceInfo))
.setIcon(pm.getApplicationIcon(sourceInfo))
.setMessage(R.string.untrusted_external_source_warning)
.setPositiveButton(R.string.external_sources_settings,
(dialog, which) -> {
Intent settingsIntent = new Intent();
settingsIntent.setAction(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
final Uri packageUri = Uri.parse("package:" + argument);
settingsIntent.setData(packageUri);
try {
getActivity().startActivityForResult(settingsIntent,
REQUEST_TRUST_EXTERNAL_SOURCE);
} catch (ActivityNotFoundException exc) {
Log.e(TAG, "Settings activity not found for action: "
+ Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
}
})
.setNegativeButton(R.string.cancel,
(dialog, which) -> getActivity().finish())
.create();
} catch (NameNotFoundException e) {
Log.e(TAG, "Did not find app info for " + argument);
getActivity().finish();
return null;
}
}
}
/**
* Superclass for all error dialogs. Stores a single CharSequence argument
*/
public abstract static class AppErrorDialog extends DialogFragment {
private static final String ARGUMENT_KEY = AppErrorDialog.class.getName() + "ARGUMENT_KEY";
protected void setArgument(@NonNull CharSequence argument) {
Bundle args = new Bundle();
args.putCharSequence(ARGUMENT_KEY, argument);
setArguments(args);
}
protected abstract Dialog createDialog(@NonNull CharSequence argument);
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return createDialog(getArguments().getString(ARGUMENT_KEY));
}
@Override
public void onCancel(DialogInterface dialog) {
getActivity().finish();
}
}
}