Merge "Cap scales used for tessellation with minimum and maximum" into lmp-dev
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 @@
     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/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index df0365e..e9297b9 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -161,11 +161,14 @@
         return result;
     }
 
+    // system APIs start here
+
     /**
      * Begin the process of restoring data from backup.  See the
      * {@link android.app.backup.RestoreSession} class for documentation on that process.
      * @hide
      */
+    @SystemApi
     public RestoreSession beginRestoreSession() {
         RestoreSession session = null;
         checkServiceBinder();
@@ -183,8 +186,6 @@
         return session;
     }
 
-    // system APIs start here
-
     /**
      * Enable/disable the backup service entirely.  When disabled, no backup
      * or restore operations will take place.  Data-changed notifications will
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 @@
 
     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 @@
     /** {@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 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 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 @@
     @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 @@
         }
     }
 
+    /** {@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 @@
          * 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 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 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 @@
             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 @@
             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 @@
             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/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c34560d..bef98d5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5065,5 +5065,5 @@
     <!-- TV content rating system strings for ZA TV -->
 
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
-    <string name="battery_saver_description">To help improve battery life, battery saver will reduce your device’s performance and restrict background data.  Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
+    <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
 </resources>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6761f24..f9baccd 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -251,10 +251,7 @@
     private int mNetworkPreference;
     private int mActiveDefaultNetwork = -1;
     // 0 is full bad, 100 is full good
-    private int mDefaultInetCondition = 0;
     private int mDefaultInetConditionPublished = 0;
-    private boolean mInetConditionChangeInFlight = false;
-    private int mDefaultConnectionSequence = 0;
 
     private Object mDnsLock = new Object();
     private int mNumDnsEntries;
@@ -274,19 +271,6 @@
     private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
 
     /**
-     * used internally to synchronize inet condition reports
-     * arg1 = networkType
-     * arg2 = condition (0 bad, 100 good)
-     */
-    private static final int EVENT_INET_CONDITION_CHANGE = 4;
-
-    /**
-     * used internally to mark the end of inet condition hold periods
-     * arg1 = networkType
-     */
-    private static final int EVENT_INET_CONDITION_HOLD_END = 5;
-
-    /**
      * used internally to clear a wakelock when transitioning
      * from one net to another.  Clear happens when we get a new
      * network - EVENT_EXPIRE_NET_TRANSITION_WAKELOCK happens
@@ -490,10 +474,6 @@
             mTypeLists[type] = new ArrayList<NetworkAgentInfo>();
         }
 
-        private boolean isDefaultNetwork(NetworkAgentInfo nai) {
-            return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
-        }
-
         public boolean isTypeSupported(int type) {
             return isNetworkTypeValid(type) && mTypeLists[type] != null;
         }
@@ -2052,6 +2032,9 @@
                 nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                         null, null);
             }
+            if (isDefaultNetwork(nai)) {
+                mDefaultInetConditionPublished = 0;
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
@@ -2222,18 +2205,6 @@
                     }
                     break;
                 }
-                case EVENT_INET_CONDITION_CHANGE: {
-                    int netType = msg.arg1;
-                    int condition = msg.arg2;
-                    handleInetConditionChange(netType, condition);
-                    break;
-                }
-                case EVENT_INET_CONDITION_HOLD_END: {
-                    int netType = msg.arg1;
-                    int sequence = msg.arg2;
-                    handleInetConditionHoldEnd(netType, sequence);
-                    break;
-                }
                 case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
                     handleDeprecatedGlobalHttpProxy();
                     break;
@@ -2428,99 +2399,15 @@
 
     // 100 percent is full good, 0 is full bad.
     public void reportInetCondition(int networkType, int percentage) {
-        if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.STATUS_BAR,
-                "ConnectivityService");
-
-        if (DBG) {
-            int pid = getCallingPid();
-            int uid = getCallingUid();
-            String s = pid + "(" + uid + ") reports inet is " +
-                (percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " +
-                "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime();
-            mInetLog.add(s);
-            while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) {
-                mInetLog.remove(0);
-            }
-        }
-        mHandler.sendMessage(mHandler.obtainMessage(
-            EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+        if (percentage > 50) return;  // don't handle good network reports
+        NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        if (nai != null) reportBadNetwork(nai.network);
     }
 
     public void reportBadNetwork(Network network) {
         //TODO
     }
 
-    private void handleInetConditionChange(int netType, int condition) {
-        if (mActiveDefaultNetwork == -1) {
-            if (DBG) log("handleInetConditionChange: no active default network - ignore");
-            return;
-        }
-        if (mActiveDefaultNetwork != netType) {
-            if (DBG) log("handleInetConditionChange: net=" + netType +
-                            " != default=" + mActiveDefaultNetwork + " - ignore");
-            return;
-        }
-        if (VDBG) {
-            log("handleInetConditionChange: net=" +
-                    netType + ", condition=" + condition +
-                    ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
-        }
-        mDefaultInetCondition = condition;
-        int delay;
-        if (mInetConditionChangeInFlight == false) {
-            if (VDBG) log("handleInetConditionChange: starting a change hold");
-            // setup a new hold to debounce this
-            if (mDefaultInetCondition > 50) {
-                delay = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
-            } else {
-                delay = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
-            }
-            mInetConditionChangeInFlight = true;
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
-                    mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
-        } else {
-            // we've set the new condition, when this hold ends that will get picked up
-            if (VDBG) log("handleInetConditionChange: currently in hold - not setting new end evt");
-        }
-    }
-
-    private void handleInetConditionHoldEnd(int netType, int sequence) {
-        if (DBG) {
-            log("handleInetConditionHoldEnd: net=" + netType +
-                    ", condition=" + mDefaultInetCondition +
-                    ", published condition=" + mDefaultInetConditionPublished);
-        }
-        mInetConditionChangeInFlight = false;
-
-        if (mActiveDefaultNetwork == -1) {
-            if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
-            return;
-        }
-        if (mDefaultConnectionSequence != sequence) {
-            if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
-            return;
-        }
-        // TODO: Figure out why this optimization sometimes causes a
-        //       change in mDefaultInetCondition to be missed and the
-        //       UI to not be updated.
-        //if (mDefaultInetConditionPublished == mDefaultInetCondition) {
-        //    if (DBG) log("no change in condition - aborting");
-        //    return;
-        //}
-        NetworkInfo networkInfo = getNetworkInfoForType(mActiveDefaultNetwork);
-        if (networkInfo.isConnected() == false) {
-            if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
-            return;
-        }
-        mDefaultInetConditionPublished = mDefaultInetCondition;
-        sendInetConditionBroadcast(networkInfo);
-        return;
-    }
-
     public ProxyInfo getProxy() {
         // this information is already available as a world read/writable jvm property
         // so this API change wouldn't have a benifit.  It also breaks the passing
@@ -4206,6 +4093,10 @@
 
     private final NetworkRequest mDefaultRequest;
 
+    private boolean isDefaultNetwork(NetworkAgentInfo nai) {
+        return mNetworkForRequestId.get(mDefaultRequest.requestId) == nai;
+    }
+
     public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             int currentScore, NetworkMisc networkMisc) {
@@ -4532,6 +4423,7 @@
                             mLegacyTypeTracker.remove(currentNetwork.networkInfo.getType(),
                                                       currentNetwork);
                         }
+                        mDefaultInetConditionPublished = 100;
                         mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
                     }
                 }
@@ -4581,8 +4473,6 @@
                 // to connected after our normal pause unless somebody reports us as
                 // really disconnected
                 mDefaultInetConditionPublished = 0;
-                mDefaultConnectionSequence++;
-                mInetConditionChangeInFlight = false;
                 // TODO - read the tcp buffer size config string from somewhere
                 // updateNetworkSettings();
             }
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 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 static boolean isStageFile(File file) {
+        return sStageFilter.accept(null, file.getName());
+    }
+
     @Deprecated
     public File allocateSessionDir() throws IOException {
         synchronized (mSessions) {
@@ -559,6 +562,15 @@
     }
 
     @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_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.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 @@
     @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 @@
                 } 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 @@
 
         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 @@
 
         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 @@
 
         Preconditions.checkNotNull(mPackageName);
         Preconditions.checkNotNull(mSignatures);
+        Preconditions.checkNotNull(mResolvedBaseCodePath);
 
-        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");
+        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 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 @@
         mPackageName = null;
         mVersionCode = -1;
         mSignatures = null;
+        mResolvedBaseCodePath = null;
 
         final File[] files = sessionStageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -445,18 +462,25 @@
                     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);
             }
-            if (!file.getName().equals(name)) {
-                file.renameTo(new File(file.getParentFile(), name));
+
+            final File targetFile = new File(sessionStageDir, targetName);
+            if (!file.equals(targetFile)) {
+                file.renameTo(targetFile);
+            }
+
+            // Base is coming from session
+            if (info.splitName == null) {
+                mResolvedBaseCodePath = targetFile.getAbsolutePath();
             }
         }
 
@@ -472,13 +496,18 @@
             }
 
         } 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 @@
         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 @@
 
     @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 @@
         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 @@
         }
 
         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;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 4f1d15e..ec284c5 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1739,7 +1739,7 @@
 
 static status_t writeLayoutClasses(
     FILE* fp, const sp<AaptAssets>& assets,
-    const sp<AaptSymbols>& symbols, int indent, bool includePrivate)
+    const sp<AaptSymbols>& symbols, int indent, bool includePrivate, bool nonConstantId)
 {
     const char* indentStr = getIndentSpace(indent);
     if (!includePrivate) {
@@ -1957,8 +1957,13 @@
                         getSymbolName(name8).string());
                 fprintf(fp, "%s*/\n", indentStr);
                 ann.printAnnotations(fp, indentStr);
+
+                const char * id_format = nonConstantId ?
+                        "%spublic static int %s_%s = %d;\n" :
+                        "%spublic static final int %s_%s = %d;\n";
+
                 fprintf(fp,
-                        "%spublic static final int %s_%s = %d;\n",
+                        id_format,
                         indentStr, nclassName.string(),
                         flattenSymbol(name8).string(), (int)pos);
             }
@@ -2177,7 +2182,7 @@
     }
 
     if (styleableSymbols != NULL) {
-        err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate);
+        err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate, nonConstantId);
         if (err != NO_ERROR) {
             return err;
         }