summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/PackageInstallerActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller/PackageInstallerActivity.java')
-rw-r--r--src/com/android/packageinstaller/PackageInstallerActivity.java793
1 files changed, 410 insertions, 383 deletions
diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java
index de685825..48f2f901 100644
--- a/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,30 +16,35 @@
*/
package com.android.packageinstaller;
-import android.app.Activity;
-import android.app.ActivityManagerNative;
+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.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
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.content.pm.VerificationParams;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.AsyncTask;
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;
@@ -48,18 +53,14 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AppSecurityPermissions;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;
-import com.android.packageinstaller.permission.utils.Utils;
+
+import com.android.packageinstaller.permission.ui.OverlayTouchActivity;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-/*
+/**
* 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
@@ -69,42 +70,47 @@ import java.io.OutputStream;
* 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 Activity implements OnCancelListener, OnClickListener {
+public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
private static final String TAG = "PackageInstaller";
- private static final int REQUEST_ENABLE_UNKNOWN_SOURCES = 1;
+ private static final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
private static final String SCHEME_FILE = "file";
- private static final String SCHEME_CONTENT = "content";
private 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 = VerificationParams.NO_UID;
- private File mContentUriApkStagingFile;
-
- private AsyncTask<Uri, Void, File> mStagingAsynTask;
+ 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;
- // View for install progress
- View mInstallConfirm;
// 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";
@@ -112,20 +118,32 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
- private static final int DLG_UNKNOWN_SOURCES = DLG_BASE + 1;
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_ADMIN_RESTRICTS_UNKNOWN_SOURCES = DLG_BASE + 6;
+ 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;
+
+ // 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);
- findViewById(R.id.spacer).setVisibility(View.GONE);
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
- tabHost.setVisibility(View.VISIBLE);
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
// If the app supports runtime permissions the new permissions will
@@ -164,9 +182,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
- } else {
- findViewById(R.id.tabscontainer).setVisibility(View.GONE);
- findViewById(R.id.spacer).setVisibility(View.VISIBLE);
}
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
@@ -188,20 +203,18 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
-
- findViewById(R.id.spacer).setVisibility(View.VISIBLE);
} else {
// This is a new application with no permissions.
msg = R.string.install_confirm_question_no_perms;
}
- tabHost.setVisibility(View.INVISIBLE);
+
+ // 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);
}
- mInstallConfirm.setVisibility(View.VISIBLE);
- mOk.setEnabled(true);
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
@@ -218,147 +231,107 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
}
}
+ /**
+ * 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) {
- // TODO better fix for this? Remove dialog so that it gets created again
- removeDialog(id);
- showDialog(id);
- }
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
- @Override
- public Dialog onCreateDialog(int id, Bundle bundle) {
- switch (id) {
- case DLG_UNKNOWN_SOURCES:
- return new AlertDialog.Builder(this)
- .setMessage(R.string.unknown_apps_dlg_text)
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- Log.i(TAG, "Finishing off activity so that user can navigate to settings manually");
- finishAffinity();
- }})
- .setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- Log.i(TAG, "Launching settings");
- launchSecuritySettings();
- }
- })
- .setOnCancelListener(this)
- .create();
- case DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES:
- return new AlertDialog.Builder(this)
- .setMessage(R.string.unknown_apps_admin_dlg_text)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- })
- .setOnCancelListener(this)
- .create();
- case DLG_PACKAGE_ERROR :
- return new AlertDialog.Builder(this)
- .setMessage(R.string.Parse_error_dlg_text)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- })
- .setOnCancelListener(this)
- .create();
- case DLG_OUT_OF_SPACE:
- // Guaranteed not to be null. will default to package name if not set by app
- CharSequence appTitle = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
- String dlgText = getString(R.string.out_of_space_dlg_text,
- appTitle.toString());
- return new AlertDialog.Builder(this)
- .setMessage(dlgText)
- .setPositiveButton(R.string.manage_applications, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- //launch manage applications
- Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- finish();
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- Log.i(TAG, "Canceling installation");
- finish();
- }
- })
- .setOnCancelListener(this)
- .create();
- case DLG_INSTALL_ERROR :
- // Guaranteed not to be null. will default to package name if not set by app
- CharSequence appTitle1 = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
- String dlgText1 = getString(R.string.install_failed_msg,
- appTitle1.toString());
- return new AlertDialog.Builder(this)
- .setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- })
- .setMessage(dlgText1)
- .setOnCancelListener(this)
- .create();
- case DLG_NOT_SUPPORTED_ON_WEAR:
- return new AlertDialog.Builder(this)
- .setMessage(R.string.wear_not_allowed_dlg_text)
- .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- setResult(RESULT_OK);
- clearCachedApkIfNeededAndFinish();
- }
- })
- .setOnCancelListener(this)
- .create();
- }
- return null;
+ Fragment currentDialog = getFragmentManager().findFragmentByTag("dialog");
+ if (currentDialog != null) {
+ transaction.remove(currentDialog);
+ }
+
+ Fragment newDialog = createDialog(id);
+
+ if (newDialog != null) {
+ transaction.add(newDialog, "dialog");
+ }
+
+ transaction.commitNowAllowingStateLoss();
}
- private void launchSecuritySettings() {
- Intent launchSettingsIntent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
- startActivityForResult(launchSettingsIntent, REQUEST_ENABLE_UNKNOWN_SOURCES);
+ /**
+ * 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_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 the settings app approved the install we are good to go regardless
- // whether the untrusted sources setting is on. This allows partners to
- // implement a "allow untrusted source once" feature.
- if (request == REQUEST_ENABLE_UNKNOWN_SOURCES && result == RESULT_OK) {
- checkIfAllowedAndInitiateInstall(true);
+ // currently just a hook for partners to implement "allow once" feature
+ // TODO: Use this to resume install request when user has explicitly trusted the source
+ // by changing the settings
+ if (request == REQUEST_TRUST_EXTERNAL_SOURCE && result == RESULT_OK) {
+ mAllowUnknownSources = true;
+
+ Fragment currentDialog = getFragmentManager().findFragmentByTag("dialog");
+ if (currentDialog != null) {
+ getFragmentManager().beginTransaction().remove(currentDialog).commit();
+ }
+
+ initiateInstall();
} else {
- clearCachedApkIfNeededAndFinish();
+ finish();
}
}
- private boolean isInstallRequestFromUnknownSource(Intent intent) {
- String callerPackage = getCallingPackage();
- if (callerPackage != null && intent.getBooleanExtra(
- Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
- try {
- mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
- if (mSourceInfo != null) {
- if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
- != 0) {
- // Privileged apps are not considered an unknown source.
- return false;
+ 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;
}
}
- } catch (NameNotFoundException e) {
}
+ Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
}
-
- return true;
+ return packagesForUid[0];
}
- /**
- * @return whether unknown sources is enabled by user in Settings
- */
- private boolean isUnknownSourcesEnabled() {
- return Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 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;
}
/**
@@ -384,7 +357,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
// 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.GET_UNINSTALLED_PACKAGES);
+ PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
@@ -406,12 +379,25 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ 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();
- mOriginatingUid = getOriginatingUid(intent);
+
+ 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;
@@ -448,74 +434,118 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
return;
}
- //set view
- setContentView(R.layout.install_start);
- mInstallConfirm = findViewById(R.id.install_confirm_panel);
- mInstallConfirm.setVisibility(View.INVISIBLE);
- mOk = (Button)findViewById(R.id.ok_button);
- mCancel = (Button)findViewById(R.id.cancel_button);
- mOk.setOnClickListener(this);
- mCancel.setOnClickListener(this);
-
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
- checkIfAllowedAndInitiateInstall(false);
+ // 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.
- *
- * @param ignoreUnknownSourcesSettings Ignore {@link #isUnknownSourcesEnabled()} and proceed
- * even if this would prevented installation.
*/
- private void checkIfAllowedAndInitiateInstall(boolean ignoreUnknownSourcesSettings) {
- // Block the install attempt on the Unknown Sources setting if necessary.
- final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(getIntent());
- if (!requestFromUnknownSource) {
+ private void checkIfAllowedAndInitiateInstall() {
+ if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
return;
}
-
- // If the admin prohibits it, or we're running in a managed profile, just show error
- // and exit. Otherwise show an option to take the user to Settings to change the setting.
- final boolean isManagedProfile = mUserManager.isManagedProfile();
+ // If the admin prohibits it, just show error and exit.
if (isUnknownSourcesDisallowed()) {
if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
- if (ignoreUnknownSourcesSettings) {
- initiateInstall();
- } else {
- showDialogInner(DLG_UNKNOWN_SOURCES);
- }
+ // Someone set user restriction via UserManager#setUserRestriction. We don't want to
+ // break apps that might already be doing this
+ showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
+ return;
} else {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
- clearCachedApkIfNeededAndFinish();
- }
- } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
- showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
- } else if (!isUnknownSourcesEnabled()) {
- if (ignoreUnknownSourcesSettings) {
- initiateInstall();
- } else {
- // Ask user to enable setting first
- showDialogInner(DLG_UNKNOWN_SOURCES);
+ finish();
}
} else {
- initiateInstall();
+ handleUnknownSources();
}
}
- @Override
- protected void onDestroy() {
- if (mStagingAsynTask != null) {
- mStagingAsynTask.cancel(true);
- mStagingAsynTask = null;
+ private void handleUnknownSources() {
+ if (mOriginatingPackage == null) {
+ Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
+ showDialogInner(DLG_ANONYMOUS_SOURCE);
+ return;
+ }
+ int appOpMode = mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+ 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(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, 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;
}
- super.onDestroy();
}
/**
@@ -529,14 +559,13 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
- final PackageUtil.AppSnippet as;
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
- | PackageManager.GET_UNINSTALLED_PACKAGES);
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
@@ -546,13 +575,13 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
- as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
+ mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
- PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
+ PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
// Check for parse errors
if (parsed == null) {
@@ -564,100 +593,20 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
- as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
+ mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
- case SCHEME_CONTENT: {
- mStagingAsynTask = new StagingAsyncTask();
- mStagingAsynTask.execute(packageUri);
- return false;
- }
-
default: {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
- clearCachedApkIfNeededAndFinish();
+ finish();
return false;
}
}
- PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
-
return true;
}
- /** Get the ApplicationInfo for the calling package, if available */
- private ApplicationInfo getSourceInfo() {
- String callingPackage = getCallingPackage();
- if (callingPackage != null) {
- try {
- return mPm.getApplicationInfo(callingPackage, 0);
- } catch (NameNotFoundException ex) {
- // ignore
- }
- }
- return null;
- }
-
-
- /** Get the originating uid if possible, or VerificationParams.NO_UID if not available */
- private int getOriginatingUid(Intent intent) {
- // The originating uid from the intent. We only trust/use this if it comes from a
- // system application
- int uidFromIntent = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- VerificationParams.NO_UID);
-
- // Get the source info from the calling package, if available. This will be the
- // definitive calling package, but it only works if the intent was started using
- // startActivityForResult,
- ApplicationInfo sourceInfo = getSourceInfo();
- if (sourceInfo != null) {
- if (uidFromIntent != VerificationParams.NO_UID &&
- (mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- return uidFromIntent;
-
- }
- // We either didn't get a uid in the intent, or we don't trust it. Use the
- // uid of the calling package instead.
- return sourceInfo.uid;
- }
-
- // We couldn't get the specific calling package. Let's get the uid instead
- int callingUid;
- try {
- callingUid = ActivityManagerNative.getDefault()
- .getLaunchedFromUid(getActivityToken());
- } catch (android.os.RemoteException ex) {
- Log.w(TAG, "Could not determine the launching uid.");
- // nothing else we can do
- return VerificationParams.NO_UID;
- }
-
- // If we got a uid from the intent, we need to verify that the caller is a
- // privileged system package before we use it
- if (uidFromIntent != VerificationParams.NO_UID) {
- String[] callingPackages = mPm.getPackagesForUid(callingUid);
- if (callingPackages != null) {
- for (String packageName: callingPackages) {
- try {
- ApplicationInfo applicationInfo =
- mPm.getApplicationInfo(packageName, 0);
-
- if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
- != 0) {
- return uidFromIntent;
- }
- } catch (NameNotFoundException ex) {
- // ignore it, and try the next package
- }
- }
- }
- }
- // We either didn't get a uid from the intent, or we don't trust it. Use the
- // calling uid instead.
- return callingUid;
- }
-
@Override
public void onBackPressed() {
if (mSessionId != -1) {
@@ -666,22 +615,19 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
super.onBackPressed();
}
- // Generic handling when pressing back key
- public void onCancel(DialogInterface dialog) {
- clearCachedApkIfNeededAndFinish();
- }
-
public void onClick(View v) {
if (v == mOk) {
- if (mOkCanInstall || mScrollView == null) {
- if (mSessionId != -1) {
- mInstaller.setPermissionsResult(mSessionId, true);
- clearCachedApkIfNeededAndFinish();
+ if (mOk.isEnabled()) {
+ if (mOkCanInstall || mScrollView == null) {
+ if (mSessionId != -1) {
+ mInstaller.setPermissionsResult(mSessionId, true);
+ finish();
+ } else {
+ startInstall();
+ }
} else {
- startInstall();
+ mScrollView.pageScroll(View.FOCUS_DOWN);
}
- } else {
- mScrollView.pageScroll(View.FOCUS_DOWN);
}
} else if (v == mCancel) {
// Cancel and finish
@@ -689,7 +635,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
- clearCachedApkIfNeededAndFinish();
+ finish();
}
}
@@ -699,7 +645,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
- newIntent.setClass(this, InstallAppProgress.class);
+ newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
@@ -708,7 +654,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
- if (mOriginatingUid != VerificationParams.NO_UID) {
+ if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
@@ -724,101 +670,182 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
finish();
}
- private void clearCachedApkIfNeededAndFinish() {
- if (mContentUriApkStagingFile != null) {
- mContentUriApkStagingFile.delete();
- mContentUriApkStagingFile = null;
+ /**
+ * 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();
}
- finish();
}
- private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> {
- private static final long SHOW_EMPTY_STATE_DELAY_MILLIS = 300;
-
- private final Runnable mEmptyStateRunnable = new Runnable() {
- @Override
- public void run() {
- ((TextView) findViewById(R.id.app_name)).setText(R.string.app_name_unknown);
- ((TextView) findViewById(R.id.install_confirm_question))
- .setText(R.string.message_staging);
- mInstallConfirm.setVisibility(View.VISIBLE);
- findViewById(android.R.id.tabhost).setVisibility(View.INVISIBLE);
- findViewById(R.id.spacer).setVisibility(View.VISIBLE);
- findViewById(R.id.ok_button).setEnabled(false);
- Drawable icon = getDrawable(R.drawable.ic_file_download);
- Utils.applyTint(PackageInstallerActivity.this,
- icon, android.R.attr.colorControlNormal);
- ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(icon);
- }
- };
+ /**
+ * 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
- protected void onPreExecute() {
- getWindow().getDecorView().postDelayed(mEmptyStateRunnable,
- SHOW_EMPTY_STATE_DELAY_MILLIS);
+ 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) getActivity())
+ .initiateInstall()))
+ .setNegativeButton(R.string.cancel, ((dialog, which) -> getActivity().finish()))
+ .create();
+ }
+ }
+
+ /**
+ * 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
- protected File doInBackground(Uri... params) {
- if (params == null || params.length <= 0) {
- return null;
- }
- Uri packageUri = params[0];
- File sourceFile = null;
- try {
- sourceFile = File.createTempFile("package", ".apk", getCacheDir());
- try (
- InputStream in = getContentResolver().openInputStream(packageUri);
- OutputStream out = (in != null) ? new FileOutputStream(
- sourceFile) : null;
- ) {
- // Despite the comments in ContentResolver#openInputStream
- // the returned stream can be null.
- if (in == null) {
- return null;
- }
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = in.read(buffer)) >= 0) {
- // Be nice and respond to a cancellation
- if (isCancelled()) {
- return null;
- }
- out.write(buffer, 0, bytesRead);
- }
- }
- } catch (IOException ioe) {
- Log.w(TAG, "Error staging apk from content URI", ioe);
- if (sourceFile != null) {
- sourceFile.delete();
- }
- }
- return sourceFile;
+ 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 void onPostExecute(File file) {
- getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
- if (isFinishing() || isDestroyed()) {
- return;
- }
- if (file == null) {
- showDialogInner(DLG_PACKAGE_ERROR);
- setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
- return;
- }
- mContentUriApkStagingFile = file;
- Uri fileUri = Uri.fromFile(file);
+ 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();
+ }
+ }
- boolean wasSetUp = processPackageUri(fileUri);
- if (wasSetUp) {
- checkIfAllowedAndInitiateInstall(false);
+ /**
+ * 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
- protected void onCancelled(File file) {
- getWindow().getDecorView().removeCallbacks(mEmptyStateRunnable);
+ public void onCancel(DialogInterface dialog) {
+ getActivity().finish();
}
- };
+ }
}