diff options
8 files changed, 148 insertions, 41 deletions
diff --git a/api/current.txt b/api/current.txt index 07974ed..e48abe2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8680,6 +8680,7 @@ package android.content.pm { field public static final int FAILURE_CONFLICT = 2; // 0x2 field public static final int FAILURE_INCOMPATIBLE = 4; // 0x4 field public static final int FAILURE_INVALID = 1; // 0x1 + field public static final int FAILURE_REJECTED = 5; // 0x5 field public static final int FAILURE_STORAGE = 3; // 0x3 field public static final int FAILURE_UNKNOWN = 0; // 0x0 } diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index cc0d569..5223476 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -36,4 +36,6 @@ interface IPackageInstaller { void uninstall(String packageName, int flags, in IPackageDeleteObserver2 observer, int userId); void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver2 observer, int userId); + + void setPermissionsResult(int sessionId, boolean accepted); } diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java index f263885..161bcde 100644 --- a/core/java/android/content/pm/InstallSessionInfo.java +++ b/core/java/android/content/pm/InstallSessionInfo.java @@ -16,7 +16,6 @@ package android.content.pm; -import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.graphics.Bitmap; @@ -33,8 +32,12 @@ public class InstallSessionInfo implements Parcelable { /** {@hide} */ public String installerPackageName; /** {@hide} */ + public String resolvedBaseCodePath; + /** {@hide} */ public float progress; /** {@hide} */ + public boolean sealed; + /** {@hide} */ public boolean open; /** {@hide} */ @@ -56,7 +59,9 @@ public class InstallSessionInfo implements Parcelable { public InstallSessionInfo(Parcel source) { sessionId = source.readInt(); installerPackageName = source.readString(); + resolvedBaseCodePath = source.readString(); progress = source.readFloat(); + sealed = source.readInt() != 0; open = source.readInt() != 0; mode = source.readInt(); @@ -149,7 +154,9 @@ public class InstallSessionInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(sessionId); dest.writeString(installerPackageName); + dest.writeString(resolvedBaseCodePath); dest.writeFloat(progress); + dest.writeInt(sealed ? 1 : 0); dest.writeInt(open ? 1 : 0); dest.writeInt(mode); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 01c080d..525142b 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -81,6 +81,10 @@ public class PackageInstaller { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; + /** {@hide} */ + public static final String + ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS"; + /** * An integer session ID. * @@ -206,6 +210,15 @@ public class PackageInstaller { } } + /** {@hide} */ + public void setPermissionsResult(int sessionId, boolean accepted) { + try { + mInstaller.setPermissionsResult(sessionId, accepted); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /** * Events for observing session lifecycle. * <p> @@ -603,9 +616,8 @@ public class PackageInstaller { * permission, incompatible certificates, etc. The user may be able to * uninstall another app to fix the issue. * <p> - * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} if one - * specific package was identified as the cause of the conflict. If - * unknown, or multiple packages, the extra may be {@code null}. + * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} with the + * specific packages identified as the cause of the conflict. */ public static final int FAILURE_CONFLICT = 2; @@ -626,6 +638,15 @@ public class PackageInstaller { */ public static final int FAILURE_INCOMPATIBLE = 4; + /** + * This install session failed because it was rejected. For example, the + * user declined requested permissions, or a package verifier rejected + * the session. + * + * @see PackageManager#VERIFICATION_REJECT + */ + public static final int FAILURE_REJECTED = 5; + public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; /** diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1e4ed31..d5604cb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -770,6 +770,9 @@ public abstract class PackageManager { */ public static final int NO_NATIVE_LIBRARIES = -114; + /** {@hide} */ + public static final int INSTALL_FAILED_REJECTED = -115; + /** * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the * package's data directory. @@ -3830,6 +3833,7 @@ public abstract class PackageManager { case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED"; case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; + case INSTALL_FAILED_REJECTED: return "INSTALL_FAILED_REJECTED"; default: return Integer.toString(status); } } @@ -3857,8 +3861,8 @@ public abstract class PackageManager { case INSTALL_FAILED_CONTAINER_ERROR: return CommitCallback.FAILURE_STORAGE; case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return CommitCallback.FAILURE_STORAGE; case INSTALL_FAILED_MEDIA_UNAVAILABLE: return CommitCallback.FAILURE_STORAGE; - case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_UNKNOWN; - case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_UNKNOWN; + case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_REJECTED; + case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_REJECTED; case INSTALL_FAILED_PACKAGE_CHANGED: return CommitCallback.FAILURE_INVALID; case INSTALL_FAILED_UID_CHANGED: return CommitCallback.FAILURE_INVALID; case INSTALL_FAILED_VERSION_DOWNGRADE: return CommitCallback.FAILURE_INVALID; @@ -3876,6 +3880,7 @@ public abstract class PackageManager { case INSTALL_FAILED_USER_RESTRICTED: return CommitCallback.FAILURE_INCOMPATIBLE; case INSTALL_FAILED_DUPLICATE_PERMISSION: return CommitCallback.FAILURE_CONFLICT; case INSTALL_FAILED_NO_MATCHING_ABIS: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_REJECTED: return CommitCallback.FAILURE_REJECTED; default: return CommitCallback.FAILURE_UNKNOWN; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index b4faea1..5c77014 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -37,7 +37,6 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; -import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; @@ -199,6 +198,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + public static boolean isStageFile(File file) { + return sStageFilter.accept(null, file.getName()); + } + @Deprecated public File allocateSessionDir() throws IOException { synchronized (mSessions) { @@ -559,6 +562,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override + public void setPermissionsResult(int sessionId, boolean accepted) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG); + + synchronized (mSessions) { + mSessions.get(sessionId).setPermissionsResult(accepted); + } + } + + @Override public void registerCallback(IPackageInstallerCallback callback, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback"); enforceCallerCanReadSessions(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5443fbc..92bb44b 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; +import static android.content.pm.PackageManager.INSTALL_FAILED_REJECTED; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; @@ -30,6 +31,7 @@ import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; import android.content.pm.InstallSessionInfo; import android.content.pm.InstallSessionParams; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; @@ -106,10 +108,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") - private boolean mPermissionsConfirmed = false; + private boolean mPermissionsAccepted = false; @GuardedBy("mLock") private boolean mDestroyed = false; + private int mFinalStatus; + private String mFinalMessage; + + /** + * Path to the resolved base APK for this session, which may point at an APK + * inside the session (when the session defines the base), or it may point + * at the existing base APK (when adding splits to an existing app). + * <p> + * This is used when confirming permissions, since we can't fully stage the + * session inside an ASEC before confirming with user. + */ + @GuardedBy("mLock") + private String mResolvedBaseCodePath; + @GuardedBy("mLock") private ArrayList<FileBridge> mBridges = new ArrayList<>(); @@ -134,12 +150,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } catch (PackageManagerException e) { Slog.e(TAG, "Install failed: " + e); destroyInternal(); - try { - mRemoteObserver.onPackageInstalled(mPackageName, e.error, e.getMessage(), - null); - } catch (RemoteException ignored) { - } - mCallback.onSessionFinished(PackageInstallerSession.this, false); + dispatchSessionFinished(e.error, e.getMessage(), null); } return true; @@ -169,9 +180,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName) == PackageManager.PERMISSION_GRANTED) { - mPermissionsConfirmed = true; + mPermissionsAccepted = true; } else { - mPermissionsConfirmed = false; + mPermissionsAccepted = false; } computeProgressLocked(); @@ -182,7 +193,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.sessionId = sessionId; info.installerPackageName = installerPackageName; + info.resolvedBaseCodePath = mResolvedBaseCodePath; info.progress = mProgress; + info.sealed = mSealed; info.open = openCount.get() > 0; info.mode = params.mode; @@ -355,11 +368,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); - - if (!mPermissionsConfirmed) { - // TODO: async confirm permissions with user - // when they confirm, we'll kick off another install() pass - throw new SecurityException("Caller must hold INSTALL permission"); + Preconditions.checkNotNull(mResolvedBaseCodePath); + + if (!mPermissionsAccepted) { + // User needs to accept permissions; give installer an intent they + // can use to involve user. + final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS); + intent.setPackage("com.android.packageinstaller"); + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + try { + mRemoteObserver.onUserActionRequired(intent); + } catch (RemoteException ignored) { + } + return; } // Inherit any packages and native libraries from existing install that @@ -386,12 +407,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { destroyInternal(); - try { - remoteObserver.onPackageInstalled(basePackageName, returnCode, msg, extras); - } catch (RemoteException ignored) { - } - final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); - mCallback.onSessionFinished(PackageInstallerSession.this, success); + dispatchSessionFinished(returnCode, msg, extras); } }; @@ -409,6 +425,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPackageName = null; mVersionCode = -1; mSignatures = null; + mResolvedBaseCodePath = null; final File[] files = sessionStageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { @@ -445,18 +462,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.signatures); // Take this opportunity to enforce uniform naming - final String name; + final String targetName; if (info.splitName == null) { - name = "base.apk"; + targetName = "base.apk"; } else { - name = "split_" + info.splitName + ".apk"; + targetName = "split_" + info.splitName + ".apk"; } - if (!FileUtils.isValidExtFilename(name)) { + if (!FileUtils.isValidExtFilename(targetName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Invalid filename: " + name); + "Invalid filename: " + targetName); + } + + final File targetFile = new File(sessionStageDir, targetName); + if (!file.equals(targetFile)) { + file.renameTo(targetFile); } - if (!file.getName().equals(name)) { - file.renameTo(new File(file.getParentFile(), name)); + + // Base is coming from session + if (info.splitName == null) { + mResolvedBaseCodePath = targetFile.getAbsolutePath(); } } @@ -472,13 +496,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } else { - // Partial installs must be consistent with existing install. + // Partial installs must be consistent with existing install final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); if (app == null) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Missing existing base package for " + mPackageName); } + // Base might be inherited from existing install + if (mResolvedBaseCodePath == null) { + mResolvedBaseCodePath = app.getBaseCodePath(); + } + final ApkLite info; try { info = PackageParser.parseApkLite(new File(app.getBaseCodePath()), @@ -537,6 +566,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage"); } + void setPermissionsResult(boolean accepted) { + if (!mSealed) { + throw new SecurityException("Must be sealed to accept permissions"); + } + + if (accepted) { + // Mark and kick off another install pass + mPermissionsAccepted = true; + mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); + } else { + destroyInternal(); + dispatchSessionFinished(INSTALL_FAILED_REJECTED, "User rejected permissions", null); + } + } + @Override public void close() { if (openCount.decrementAndGet() == 0) { @@ -546,11 +590,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void abandon() { - try { - destroyInternal(); - } finally { - mCallback.onSessionFinished(this, false); + destroyInternal(); + dispatchSessionFinished(INSTALL_FAILED_INTERNAL_ERROR, "Session was abandoned", null); + } + + private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { + mFinalStatus = returnCode; + mFinalMessage = msg; + + if (mRemoteObserver != null) { + try { + mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); + } catch (RemoteException ignored) { + } } + + final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); + mCallback.onSessionFinished(this, success); } private void destroyInternal() { @@ -578,9 +634,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mClientProgress", mClientProgress); pw.printPair("mProgress", mProgress); pw.printPair("mSealed", mSealed); - pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed); + pw.printPair("mPermissionsAccepted", mPermissionsAccepted); pw.printPair("mDestroyed", mDestroyed); pw.printPair("mBridges", mBridges.size()); + pw.printPair("mFinalStatus", mFinalStatus); + pw.printPair("mFinalMessage", mFinalMessage); pw.println(); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4bf6636..6802fac 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4084,7 +4084,8 @@ public class PackageManagerService extends IPackageManager.Stub { } for (File file : files) { - final boolean isPackage = isApkFile(file) || file.isDirectory(); + final boolean isPackage = (isApkFile(file) || file.isDirectory()) + && !PackageInstallerService.isStageFile(file); if (!isPackage) { // Ignore entries which are not apk's continue; |