Merge "Federate platform docs against support library API file" into stage-aosp-master
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 5f61e07..fc697a3 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -19,6 +19,7 @@
 import android.os.FileUtils;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.system.ErrnoException;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -26,9 +27,10 @@
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
+import libcore.io.Libcore;
+
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -148,22 +150,50 @@
             // The dex path is not a secondary dex file. Nothing to do.
             return;
         }
-        File secondaryProfile = getSecondaryProfileFile(dexPath);
+
+        File realDexPath;
         try {
-            // Create the profile if not already there.
-            // Returns true if the file was created, false if the file already exists.
-            // or throws exceptions in case of errors.
+            // Secondary dex profiles are stored in the oat directory, next to the real dex file
+            // and have the same name with 'cur.prof' appended. We use the realpath because that
+            // is what installd is using when processing the dex file.
+            // NOTE: Keep in sync with installd.
+            realDexPath = new File(Libcore.os.realpath(dexPath));
+        } catch (ErrnoException ex) {
+            Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
+                    + ":" + ex.getMessage());
+            // Do not continue with registration if we could not retrieve the real path.
+            return;
+        }
+
+        // NOTE: Keep this in sync with installd expectations.
+        File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
+        File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+
+        // Create the profile if not already there.
+        // Returns true if the file was created, false if the file already exists.
+        // or throws exceptions in case of errors.
+        if (!secondaryProfileDir.exists()) {
+            if (!secondaryProfileDir.mkdir()) {
+                Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile);
+                // Do not continue with registration if we could not create the oat dir.
+                return;
+            }
+        }
+
+        try {
             boolean created = secondaryProfile.createNewFile();
             if (DEBUG && created) {
                 Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
             }
         } catch (IOException ex) {
-            Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile +
-                    ":" + ex.getMessage());
-            // Don't move forward with the registration if we failed to create the profile.
+            Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath
+                    + ":" + ex.getMessage());
+            // Do not continue with registration if we could not create the profile files.
             return;
         }
 
+        // If we got here, the dex paths is a secondary dex and we were able to create the profile.
+        // Register the path to the runtime.
         VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath });
     }
 
@@ -177,11 +207,4 @@
         }
         return false;
     }
-
-    // Secondary dex profiles are stored next to the dex file and have the same
-    // name with '.prof' appended.
-    // NOTE: Keep in sync with installd.
-    private File getSecondaryProfileFile(String dexPath) {
-        return new File(dexPath + ".prof");
-    }
 }
diff --git a/core/java/android/app/usage/ExternalStorageStats.java b/core/java/android/app/usage/ExternalStorageStats.java
index d7e570f..f00e5c2 100644
--- a/core/java/android/app/usage/ExternalStorageStats.java
+++ b/core/java/android/app/usage/ExternalStorageStats.java
@@ -33,6 +33,7 @@
     /** {@hide} */ public long videoBytes;
     /** {@hide} */ public long imageBytes;
     /** {@hide} */ public long appBytes;
+    /** {@hide} */ public long obbBytes;
 
     /**
      * Return the total bytes used by all files in the shared/external storage
@@ -97,6 +98,11 @@
     }
 
     /** {@hide} */
+    public @BytesLong long getObbBytes() {
+        return obbBytes;
+    }
+
+    /** {@hide} */
     public ExternalStorageStats() {
     }
 
@@ -107,6 +113,7 @@
         this.videoBytes = in.readLong();
         this.imageBytes = in.readLong();
         this.appBytes = in.readLong();
+        this.obbBytes = in.readLong();
     }
 
     @Override
@@ -121,6 +128,7 @@
         dest.writeLong(videoBytes);
         dest.writeLong(imageBytes);
         dest.writeLong(appBytes);
+        dest.writeLong(obbBytes);
     }
 
     public static final Creator<ExternalStorageStats> CREATOR = new Creator<ExternalStorageStats>() {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4b44a17..64d687e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -508,21 +508,13 @@
              in boolean isSharedModule, IDexModuleRegisterCallback callback);
 
     /**
-     * Ask the package manager to perform a dex-opt for the given reason. The package
-     * manager will map the reason to a compiler filter according to the current system
-     * configuration.
-     */
-    boolean performDexOpt(String packageName, boolean checkProfiles,
-            int compileReason, boolean force, boolean bootComplete);
-
-    /**
      * Ask the package manager to perform a dex-opt with the given compiler filter.
      *
      * Note: exposed only for the shell command to allow moving packages explicitly to a
      *       definite state.
      */
     boolean performDexOptMode(String packageName, boolean checkProfiles,
-            String targetCompilerFilter, boolean force, boolean bootComplete);
+            String targetCompilerFilter, boolean force, boolean bootComplete, String splitName);
 
     /**
      * Ask the package manager to perform a dex-opt with the given compiler filter on the
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 270e63f..5e2a081 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -16,10 +16,10 @@
 
 package android.os;
 
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
 import libcore.util.NativeAllocationRegistry;
 
+import java.util.NoSuchElementException;
+
 /** @hide */
 public abstract class HwBinder implements IHwBinder {
     private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@
     public native final void registerService(String serviceName)
         throws RemoteException;
 
-    public static native final IHwBinder getService(
+    public static final IHwBinder getService(
             String iface,
             String serviceName)
+        throws RemoteException, NoSuchElementException {
+        return getService(iface, serviceName, false /* retry */);
+    }
+    public static native final IHwBinder getService(
+            String iface,
+            String serviceName,
+            boolean retry)
         throws RemoteException, NoSuchElementException;
 
     public static native final void configureRpcThreadpool(
diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java
index 88226f0..5e9b9ae3 100644
--- a/core/java/android/os/HwBlob.java
+++ b/core/java/android/os/HwBlob.java
@@ -43,6 +43,18 @@
     public native final double getDouble(long offset);
     public native final String getString(long offset);
 
+    /**
+      The copyTo... methods copy the blob's data, starting from the given
+      byte offset, into the array. A total of "size" _elements_ are copied.
+     */
+    public native final void copyToBoolArray(long offset, boolean[] array, int size);
+    public native final void copyToInt8Array(long offset, byte[] array, int size);
+    public native final void copyToInt16Array(long offset, short[] array, int size);
+    public native final void copyToInt32Array(long offset, int[] array, int size);
+    public native final void copyToInt64Array(long offset, long[] array, int size);
+    public native final void copyToFloatArray(long offset, float[] array, int size);
+    public native final void copyToDoubleArray(long offset, double[] array, int size);
+
     public native final void putBool(long offset, boolean x);
     public native final void putInt8(long offset, byte x);
     public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@
     public native final void putDouble(long offset, double x);
     public native final void putString(long offset, String x);
 
+    public native final void putBoolArray(long offset, boolean[] x);
+    public native final void putInt8Array(long offset, byte[] x);
+    public native final void putInt16Array(long offset, short[] x);
+    public native final void putInt32Array(long offset, int[] x);
+    public native final void putInt64Array(long offset, long[] x);
+    public native final void putFloatArray(long offset, float[] x);
+    public native final void putDoubleArray(long offset, double[] x);
+
     public native final void putBlob(long offset, HwBlob blob);
 
     public native final long handle();
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 81ea191..36e4c1c6 100644
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -27,9 +27,6 @@
 
     PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride);
     ObbInfo getObbInfo(String filename);
-    long calculateDirectorySize(String directory);
-    /** Return file system stats: [0] is total bytes, [1] is available bytes */
-    long[] getFileSystemStats(String path);
     void clearDirectory(String directory);
     long calculateInstalledSize(String packagePath, boolean isForwardLocked, String abiOverride);
 }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 6d8b811..429a1d9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -546,12 +546,13 @@
         for (String classPathElement : classPathElements) {
             // System server is fully AOTed and never profiled
             // for profile guided compilation.
-            // TODO: Make this configurable between INTERPRET_ONLY, SPEED, SPACE and EVERYTHING?
+            String systemServerFilter = SystemProperties.get(
+                    "dalvik.vm.systemservercompilerfilter", "speed");
 
             int dexoptNeeded;
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
-                    classPathElement, instructionSet, "speed",
+                    classPathElement, instructionSet, systemServerFilter,
                     false /* newProfile */, false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
@@ -570,13 +571,13 @@
                 final String packageName = "*";
                 final String outputPath = null;
                 final int dexFlags = 0;
-                final String compilerFilter = "speed";
+                final String compilerFilter = systemServerFilter;
                 final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
                 final String seInfo = null;
                 try {
                     installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
                             instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
-                            uuid, sharedLibraries, seInfo);
+                            uuid, sharedLibraries, seInfo, false /* downgrade */);
                 } catch (RemoteException | ServiceSpecificException e) {
                     // Ignore (but log), we need this on the classpath for fallback mode.
                     Log.w(TAG, "Failed compiling classpath element for system server: "
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index c4f22ee..08d9527 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -296,7 +296,8 @@
         JNIEnv *env,
         jclass /* clazzObj */,
         jstring ifaceNameObj,
-        jstring serviceNameObj) {
+        jstring serviceNameObj,
+        jboolean retry) {
 
     using ::android::hidl::base::V1_0::IBase;
     using ::android::hardware::details::getRawServiceInternal;
@@ -319,8 +320,7 @@
         serviceName = str.c_str();
     }
 
-    // TODO(b/67981006): true /* retry */
-    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
+    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, retry /* retry */, false /* getStub */);
     sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
@@ -360,7 +360,7 @@
     { "registerService", "(Ljava/lang/String;)V",
         (void *)JHwBinder_native_registerService },
 
-    { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
+    { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;",
         (void *)JHwBinder_native_getService },
 
     { "configureRpcThreadpool", "(JZ)V",
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index 40d49b7..737ec47 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -60,12 +60,12 @@
         JNIEnv *env, jobject thiz, const sp<JHwBlob> &context) {
     sp<JHwBlob> old = (JHwBlob *)env->GetLongField(thiz, gFields.contextID);
 
-    if (context != NULL) {
-        context->incStrong(NULL /* id */);
+    if (context != nullptr) {
+        context->incStrong(nullptr /* id */);
     }
 
-    if (old != NULL) {
-        old->decStrong(NULL /* id */);
+    if (old != nullptr) {
+        old->decStrong(nullptr /* id */);
     }
 
     env->SetLongField(thiz, gFields.contextID, (long)context.get());
@@ -150,6 +150,10 @@
     return mBuffer;
 }
 
+void *JHwBlob::data() {
+    return mBuffer;
+}
+
 size_t JHwBlob::size() const {
     return mSize;
 }
@@ -242,8 +246,8 @@
 static void releaseNativeContext(void *nativeContext) {
     sp<JHwBlob> parcel = (JHwBlob *)nativeContext;
 
-    if (parcel != NULL) {
-        parcel->decStrong(NULL /* id */);
+    if (parcel != nullptr) {
+        parcel->decStrong(nullptr /* id */);
     }
 }
 
@@ -313,6 +317,82 @@
     return env->NewStringUTF(s->c_str());
 }
 
+#define DEFINE_BLOB_ARRAY_COPIER(Suffix,Type,NewType)                          \
+static void JHwBlob_native_copyTo ## Suffix ## Array(                          \
+        JNIEnv *env,                                                           \
+        jobject thiz,                                                          \
+        jlong offset,                                                          \
+        Type ## Array array,                                                   \
+        jint size) {                                                           \
+    if (array == nullptr) {                                                    \
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);     \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    if (env->GetArrayLength(array) < size) {                                   \
+        signalExceptionForError(env, BAD_VALUE);                               \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);                   \
+                                                                               \
+    if ((offset + size * sizeof(Type)) > blob->size()) {                       \
+        signalExceptionForError(env, -ERANGE);                                 \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    env->Set ## NewType ## ArrayRegion(                                        \
+            array,                                                             \
+            0 /* start */,                                                     \
+            size,                                                              \
+            reinterpret_cast<const Type *>(                                    \
+                static_cast<const uint8_t *>(blob->data()) + offset));         \
+}
+
+static void JHwBlob_native_copyToBoolArray(
+        JNIEnv *env,
+        jobject thiz,
+        jlong offset,
+        jbooleanArray array,
+        jint size) {
+    if (array == nullptr) {
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);
+        return;
+    }
+
+    if (env->GetArrayLength(array) < size) {
+        signalExceptionForError(env, BAD_VALUE);
+        return;
+    }
+
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+    if ((offset + size * sizeof(bool)) > blob->size()) {
+        signalExceptionForError(env, -ERANGE);
+        return;
+    }
+
+    const bool *src =
+        reinterpret_cast<const bool *>(
+                static_cast<const uint8_t *>(blob->data()) + offset);
+
+    jboolean *dst = env->GetBooleanArrayElements(array, nullptr /* isCopy */);
+
+    for (jint i = 0; i < size; ++i) {
+        dst[i] = src[i];
+    }
+
+    env->ReleaseBooleanArrayElements(array, dst, 0 /* mode */);
+    dst = nullptr;
+}
+
+DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double)
+
 #define DEFINE_BLOB_PUTTER(Suffix,Type)                                        \
 static void JHwBlob_native_put ## Suffix(                                      \
         JNIEnv *env, jobject thiz, jlong offset, Type x) {                     \
@@ -375,6 +455,72 @@
     blob->putBlob(offset + hidl_string::kOffsetOfBuffer, subBlob);
 }
 
+#define DEFINE_BLOB_ARRAY_PUTTER(Suffix,Type,NewType)                          \
+static void JHwBlob_native_put ## Suffix ## Array(                             \
+        JNIEnv *env, jobject thiz, jlong offset, Type ## Array array) {        \
+                                                                               \
+    if (array == nullptr) {                                                    \
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);     \
+        return;                                                                \
+    }                                                                          \
+                                                                               \
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);                   \
+                                                                               \
+    jsize len = env->GetArrayLength(array);                                    \
+                                                                               \
+    Type *src =                                                                \
+        env->Get ## NewType ## ArrayElements(array, nullptr /* isCopy */);     \
+                                                                               \
+    status_t err = blob->write(offset, src, len * sizeof(Type));               \
+                                                                               \
+    env->Release ## NewType ## ArrayElements(array, src, 0 /* mode */);        \
+    src = nullptr;                                                             \
+                                                                               \
+    if (err != OK) {                                                           \
+        signalExceptionForError(env, err);                                     \
+    }                                                                          \
+}
+
+DEFINE_BLOB_ARRAY_PUTTER(Int8,jbyte,Byte)
+DEFINE_BLOB_ARRAY_PUTTER(Int16,jshort,Short)
+DEFINE_BLOB_ARRAY_PUTTER(Int32,jint,Int)
+DEFINE_BLOB_ARRAY_PUTTER(Int64,jlong,Long)
+DEFINE_BLOB_ARRAY_PUTTER(Float,jfloat,Float)
+DEFINE_BLOB_ARRAY_PUTTER(Double,jdouble,Double)
+
+static void JHwBlob_native_putBoolArray(
+        JNIEnv *env, jobject thiz, jlong offset, jbooleanArray array) {
+
+    if (array == nullptr) {
+        jniThrowException(env, "java/lang/NullPointerException", nullptr);
+        return;
+    }
+
+    sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+    jsize len = env->GetArrayLength(array);
+
+    if ((offset + len * sizeof(bool)) > blob->size()) {
+        signalExceptionForError(env, -ERANGE);
+        return;
+    }
+
+    const jboolean *src =
+        env->GetBooleanArrayElements(array, nullptr /* isCopy */);
+
+    bool *dst = reinterpret_cast<bool *>(
+            static_cast<uint8_t *>(blob->data()) + offset);
+
+    for (jsize i = 0; i < len; ++i) {
+        dst[i] = src[i];
+    }
+
+    env->ReleaseBooleanArrayElements(
+            array, const_cast<jboolean *>(src), 0 /* mode */);
+
+    src = nullptr;
+}
+
 static void JHwBlob_native_putBlob(
         JNIEnv *env, jobject thiz, jlong offset, jobject blobObj) {
     if (blobObj == nullptr) {
@@ -413,6 +559,14 @@
     { "getDouble", "(J)D", (void *)JHwBlob_native_getDouble },
     { "getString", "(J)Ljava/lang/String;", (void *)JHwBlob_native_getString },
 
+    { "copyToBoolArray", "(J[ZI)V", (void *)JHwBlob_native_copyToBoolArray },
+    { "copyToInt8Array", "(J[BI)V", (void *)JHwBlob_native_copyToInt8Array },
+    { "copyToInt16Array", "(J[SI)V", (void *)JHwBlob_native_copyToInt16Array },
+    { "copyToInt32Array", "(J[II)V", (void *)JHwBlob_native_copyToInt32Array },
+    { "copyToInt64Array", "(J[JI)V", (void *)JHwBlob_native_copyToInt64Array },
+    { "copyToFloatArray", "(J[FI)V", (void *)JHwBlob_native_copyToFloatArray },
+    { "copyToDoubleArray", "(J[DI)V", (void *)JHwBlob_native_copyToDoubleArray },
+
     { "putBool", "(JZ)V", (void *)JHwBlob_native_putBool },
     { "putInt8", "(JB)V", (void *)JHwBlob_native_putInt8 },
     { "putInt16", "(JS)V", (void *)JHwBlob_native_putInt16 },
@@ -422,6 +576,14 @@
     { "putDouble", "(JD)V", (void *)JHwBlob_native_putDouble },
     { "putString", "(JLjava/lang/String;)V", (void *)JHwBlob_native_putString },
 
+    { "putBoolArray", "(J[Z)V", (void *)JHwBlob_native_putBoolArray },
+    { "putInt8Array", "(J[B)V", (void *)JHwBlob_native_putInt8Array },
+    { "putInt16Array", "(J[S)V", (void *)JHwBlob_native_putInt16Array },
+    { "putInt32Array", "(J[I)V", (void *)JHwBlob_native_putInt32Array },
+    { "putInt64Array", "(J[J)V", (void *)JHwBlob_native_putInt64Array },
+    { "putFloatArray", "(J[F)V", (void *)JHwBlob_native_putFloatArray },
+    { "putDoubleArray", "(J[D)V", (void *)JHwBlob_native_putDoubleArray },
+
     { "putBlob", "(JL" PACKAGE_PATH "/HwBlob;)V",
         (void *)JHwBlob_native_putBlob },
 
diff --git a/core/jni/android_os_HwBlob.h b/core/jni/android_os_HwBlob.h
index 39393cb..6b1db63 100644
--- a/core/jni/android_os_HwBlob.h
+++ b/core/jni/android_os_HwBlob.h
@@ -50,6 +50,8 @@
             size_t offset, const android::hardware::hidl_string **s) const;
 
     const void *data() const;
+    void *data();
+
     size_t size() const;
 
     status_t putBlob(size_t offset, const sp<JHwBlob> &blob);
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 9347877..3800e6f 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -213,27 +213,6 @@
         }
 
         @Override
-        public long calculateDirectorySize(String path) throws RemoteException {
-            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
-            final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
-            if (dir.exists() && dir.isDirectory()) {
-                final String targetPath = dir.getAbsolutePath();
-                return MeasurementUtils.measureDirectory(targetPath);
-            } else {
-                return 0L;
-            }
-        }
-
-        @Override
-        public long[] getFileSystemStats(String path) {
-            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
-            final File file = new File(path);
-            return new long[] { file.getTotalSpace(), file.getUsableSpace() };
-        }
-
-        @Override
         public void clearDirectory(String path) throws RemoteException {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 6749afb..914e81e 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 
-import android.app.AlarmManager;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -38,8 +37,10 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.LocalServices;
 import com.android.server.PinnerService;
+import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.TimeUnit;
 
@@ -73,6 +74,9 @@
     // Optimizations should be aborted. No space left on device.
     private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
 
+    // Used for calculating space threshold for downgrading unused apps.
+    private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
+
     /**
      * Set of failed packages remembered across job runs.
      */
@@ -92,6 +96,9 @@
 
     private final File mDataDir = Environment.getDataDirectory();
 
+    private static final long mDowngradeUnusedAppsThresholdInMillis =
+            getDowngradeUnusedAppsThresholdInMillis();
+
     public static void schedule(Context context) {
         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
 
@@ -211,11 +218,10 @@
             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
             // trade-off worth doing to save boot time work.
-            int result = pm.performDexOptWithStatus(pkg,
-                    /* checkProfiles */ false,
+            int result = pm.performDexOptWithStatus(new DexoptOptions(
+                    pkg,
                     PackageManagerService.REASON_BOOT,
-                    /* force */ false,
-                    /* bootComplete */ true);
+                    DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
             }
@@ -243,7 +249,8 @@
     }
 
     // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
-    private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) {
+    private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
+            Context context) {
         Log.i(TAG, "Performing idle optimizations");
         // If post-boot update is still running, request that it exits early.
         mExitPostBootUpdate.set(true);
@@ -274,9 +281,16 @@
             long lowStorageThreshold, boolean is_for_primary_dex,
             ArraySet<String> failedPackageNames) {
         ArraySet<String> updatedPackages = new ArraySet<>();
+        Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+        // Only downgrade apps when space is low on device.
+        // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
+        // up disk before user hits the actual lowStorageThreshold.
+        final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
+                lowStorageThreshold;
+        boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
         for (String pkg : pkgs) {
             int abort_code = abortIdleOptimizations(lowStorageThreshold);
-            if (abort_code != OPTIMIZE_CONTINUE) {
+            if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                 return abort_code;
             }
 
@@ -284,30 +298,57 @@
                 if (failedPackageNames.contains(pkg)) {
                     // Skip previously failing package
                     continue;
-                } else {
-                    // Conservatively add package to the list of failing ones in case performDexOpt
-                    // never returns.
-                    failedPackageNames.add(pkg);
                 }
             }
 
+            int reason;
+            boolean downgrade;
+            // Downgrade unused packages.
+            if (unusedPackages.contains(pkg) && shouldDowngrade) {
+                // This applies for system apps or if packages location is not a directory, i.e.
+                // monolithic install.
+                if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
+                    // For apps that don't have the oat directory, instead of downgrading,
+                    // remove their compiler artifacts from dalvik cache.
+                    pm.deleteOatArtifactsOfPackage(pkg);
+                    continue;
+                } else {
+                    reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+                    downgrade = true;
+                }
+            } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
+                reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+                downgrade = false;
+            } else {
+                // can't dexopt because of low space.
+                continue;
+            }
+
+            synchronized (failedPackageNames) {
+                // Conservatively add package to the list of failing ones in case
+                // performDexOpt never returns.
+                failedPackageNames.add(pkg);
+            }
+
             // Optimize package if needed. Note that there can be no race between
             // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
             boolean success;
+            int dexoptFlags =
+                    DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
             if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(pkg,
-                        /* checkProfiles */ true,
+                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg,
                         PackageManagerService.REASON_BACKGROUND_DEXOPT,
-                        /* force */ false,
-                        /* bootComplete */ true);
+                        dexoptFlags));
                 success = result != PackageDexOptimizer.DEX_OPT_FAILED;
                 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
                     updatedPackages.add(pkg);
                 }
             } else {
-                success = pm.performDexOptSecondary(pkg,
+                success = pm.performDexOpt(new DexoptOptions(pkg,
                         PackageManagerService.REASON_BACKGROUND_DEXOPT,
-                        /* force */ false);
+                        dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
             }
             if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
@@ -347,6 +388,16 @@
         return OPTIMIZE_CONTINUE;
     }
 
+    // Evaluate whether apps should be downgraded.
+    private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
+        long usableSpace = mDataDir.getUsableSpace();
+        if (usableSpace < lowStorageThresholdForDowngrade) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Execute the idle optimizations immediately.
      */
@@ -415,4 +466,14 @@
             pinnerService.update(updatedPackages);
         }
     }
+
+    private static long getDowngradeUnusedAppsThresholdInMillis() {
+        final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
+        String sysPropValue = SystemProperties.get(sysPropKey);
+        if (sysPropValue == null || sysPropValue.isEmpty()) {
+            Log.w(TAG, "SysProp " + sysPropKey + " not set");
+            return Long.MAX_VALUE;
+        }
+        return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5c4c040..371b3ef 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -258,7 +258,7 @@
 
     public long[] getExternalSize(String uuid, int userId, int flags, int[] appIds)
             throws InstallerException {
-        if (!checkBeforeRemote()) return new long[4];
+        if (!checkBeforeRemote()) return new long[6];
         try {
             return mInstalld.getExternalSize(uuid, userId, flags, appIds);
         } catch (Exception e) {
@@ -279,13 +279,13 @@
     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
-            @Nullable String seInfo)
+            @Nullable String seInfo, boolean downgrade)
             throws InstallerException {
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
-                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo);
+                    dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index a43f8af..241d76f 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -30,17 +29,16 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.storage.StorageManager;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -261,11 +259,12 @@
             public void dexopt(String apkPath, int uid, @Nullable String pkgName,
                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
-                    @Nullable String sharedLibraries, @Nullable String seInfo) throws InstallerException {
+                    @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade)
+                    throws InstallerException {
                 final StringBuilder builder = new StringBuilder();
 
-                // The version. Right now it's 2.
-                builder.append("2 ");
+                // The version. Right now it's 3.
+                builder.append("3 ");
 
                 builder.append("dexopt");
 
@@ -280,6 +279,7 @@
                 encodeParameter(builder, volumeUuid);
                 encodeParameter(builder, sharedLibraries);
                 encodeParameter(builder, seInfo);
+                encodeParameter(builder, downgrade);
 
                 commands.add(builder.toString());
             }
@@ -314,12 +314,18 @@
             libraryDependencies = NO_LIBRARIES;
         }
 
+
         optimizer.performDexOpt(pkg, libraryDependencies,
-                null /* ISAs */, false /* checkProfiles */,
-                getCompilerFilterForReason(compilationReason),
+                null /* ISAs */,
                 null /* CompilerStats.PackageStats */,
                 mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName),
-                true /* bootComplete */);
+                new DexoptOptions(pkg.packageName, compilationReason,
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
+
+        mPackageManagerService.getDexManager().dexoptSecondaryDex(
+                new DexoptOptions(pkg.packageName, compilationReason,
+                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
         return commands;
     }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 7e9596a..e53a08a 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,9 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
-import android.os.Environment;
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -30,11 +28,12 @@
 import android.os.WorkSource;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.dex.DexoptUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -123,17 +122,16 @@
      * synchronized on {@link #mInstallLock}.
      */
     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
-            String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
-            CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps,
-            boolean bootComplete) {
+            String[] instructionSets, CompilerStats.PackageStats packageStats,
+            boolean isUsedByOtherApps, DexoptOptions options) {
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
         synchronized (mInstallLock) {
             final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid);
             try {
-                return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
-                        targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete);
+                return performDexOptLI(pkg, sharedLibraries, instructionSets,
+                        packageStats, isUsedByOtherApps, options);
             } finally {
                 releaseWakeLockLI(acquireTime);
             }
@@ -146,9 +144,8 @@
      */
     @GuardedBy("mInstallLock")
     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
-            String[] targetInstructionSets, boolean checkForProfileUpdates,
-            String targetCompilerFilter, CompilerStats.PackageStats packageStats,
-            boolean isUsedByOtherApps, boolean bootComplete) {
+            String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
+            boolean isUsedByOtherApps, DexoptOptions options) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
@@ -156,16 +153,18 @@
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 
         final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
-                targetCompilerFilter, isUsedByOtherApps);
-        final boolean profileUpdated = checkForProfileUpdates &&
+                options.getCompilerFilter(), isUsedByOtherApps);
+        final boolean profileUpdated = options.isCheckForProfileUpdates() &&
                 isProfileUpdated(pkg, sharedGid, compilerFilter);
 
-        final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
-        final int dexoptFlags = getDexFlags(pkg, compilerFilter, bootComplete);
-        // Get the dependencies of each split in the package. For each code path in the package,
-        // this array contains the relative paths of each split it depends on, separated by colons.
-        String[] splitDependencies = getSplitDependencies(pkg);
+        final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+
+        // Get the class loader context dependencies.
+        // For each code path in the package, this array contains the class loader context that
+        // needs to be passed to dexopt in order to ensure correct optimizations.
+        String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts(
+                pkg.applicationInfo, sharedLibraries);
 
         int result = DEX_OPT_SKIPPED;
         for (int i = 0; i < paths.size(); i++) {
@@ -176,16 +175,18 @@
             }
             // Append shared libraries with split dependencies for this split.
             String path = paths.get(i);
-            String sharedLibrariesPathWithSplits;
-            if (sharedLibrariesPath != null && splitDependencies[i] != null) {
-                sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i];
-            } else {
-                sharedLibrariesPathWithSplits =
-                        splitDependencies[i] != null ? splitDependencies[i] : sharedLibrariesPath;
+            if (options.getSplitName() != null) {
+                // We are asked to compile only a specific split. Check that the current path is
+                // what we are looking for.
+                if (!options.getSplitName().equals(new File(path).getName())) {
+                    continue;
+                }
             }
+
             for (String dexCodeIsa : dexCodeInstructionSets) {
-                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
-                        sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats);
+                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
+                        profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
+                        packageStats, options.isDowngrade());
                 // The end result is:
                 //  - FAILED if any path failed,
                 //  - PERFORMED if at least one path needed compilation,
@@ -209,8 +210,8 @@
     @GuardedBy("mInstallLock")
     private int dexOptPath(PackageParser.Package pkg, String path, String isa,
             String compilerFilter, boolean profileUpdated, String sharedLibrariesPath,
-            int dexoptFlags, int uid, CompilerStats.PackageStats packageStats) {
-        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated);
+            int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
+        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade);
         if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
             return DEX_OPT_SKIPPED;
         }
@@ -229,8 +230,12 @@
         try {
             long startTime = System.currentTimeMillis();
 
+            // TODO: Consider adding 2 different APIs for primary and secondary dexopt.
+            // installd only uses downgrade flag for secondary dex files and ignores it for
+            // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
-                    compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo);
+                    compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo,
+                    false /* downgrade*/);
 
             if (packageStats != null) {
                 long endTime = System.currentTimeMillis();
@@ -258,12 +263,12 @@
      * that seems wasteful.
      */
     public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
-            String compilerFilter, boolean isUsedByOtherApps) {
+            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
         synchronized (mInstallLock) {
             final long acquireTime = acquireWakeLockLI(info.uid);
             try {
                 return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
-                        isUsedByOtherApps);
+                        isUsedByOtherApps, downgrade);
             } finally {
                 releaseWakeLockLI(acquireTime);
             }
@@ -305,7 +310,7 @@
 
     @GuardedBy("mInstallLock")
     private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
-            String compilerFilter, boolean isUsedByOtherApps) {
+            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
         compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
         // Secondary dex files are currently not compiled at boot.
@@ -335,7 +340,8 @@
                 // TODO(calin): maybe add a separate call.
                 mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                         /*oatDir*/ null, dexoptFlags,
-                        compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser);
+                        compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser,
+                        downgrade);
             }
 
             return DEX_OPT_PERFORMED;
@@ -436,11 +442,11 @@
      * configuration (isa, compiler filter, profile).
      */
     private int getDexoptNeeded(String path, String isa, String compilerFilter,
-            boolean newProfile) {
+            boolean newProfile, boolean downgrade) {
         int dexoptNeeded;
         try {
-          dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
-              false /* downgrade */);
+            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
+                    downgrade);
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
@@ -449,86 +455,6 @@
     }
 
     /**
-     * Computes the shared libraries path that should be passed to dexopt.
-     */
-    private String getSharedLibrariesPath(String[] sharedLibraries) {
-        if (sharedLibraries == null || sharedLibraries.length == 0) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        for (String lib : sharedLibraries) {
-            if (sb.length() != 0) {
-                sb.append(":");
-            }
-            sb.append(lib);
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Walks dependency tree and gathers the dependencies for each split in a split apk.
-     * The split paths are stored as relative paths, separated by colons.
-     */
-    private String[] getSplitDependencies(PackageParser.Package pkg) {
-        // Convert all the code paths to relative paths.
-        String baseCodePath = new File(pkg.baseCodePath).getParent();
-        List<String> paths = pkg.getAllCodePaths();
-        String[] splitDependencies = new String[paths.size()];
-        for (int i = 0; i < paths.size(); i++) {
-            File pathFile = new File(paths.get(i));
-            String fileName = pathFile.getName();
-            paths.set(i, fileName);
-
-            // Sanity check that the base paths of the splits are all the same.
-            String basePath = pathFile.getParent();
-            if (!basePath.equals(baseCodePath)) {
-                Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
-                        baseCodePath);
-            }
-        }
-
-        // If there are no other dependencies, fill in the implicit dependency on the base apk.
-        SparseArray<int[]> dependencies = pkg.applicationInfo.splitDependencies;
-        if (dependencies == null) {
-            for (int i = 1; i < paths.size(); i++) {
-                splitDependencies[i] = paths.get(0);
-            }
-            return splitDependencies;
-        }
-
-        // Fill in the dependencies, skipping the base apk which has no dependencies.
-        for (int i = 1; i < dependencies.size(); i++) {
-            getParentDependencies(dependencies.keyAt(i), paths, dependencies, splitDependencies);
-        }
-
-        return splitDependencies;
-    }
-
-    /**
-     * Recursive method to generate dependencies for a particular split.
-     * The index is a key from the package's splitDependencies.
-     */
-    private String getParentDependencies(int index, List<String> paths,
-            SparseArray<int[]> dependencies, String[] splitDependencies) {
-        // The base apk is always first, and has no dependencies.
-        if (index == 0) {
-            return null;
-        }
-        // Return the result if we've computed the dependencies for this index already.
-        if (splitDependencies[index] != null) {
-            return splitDependencies[index];
-        }
-        // Get the dependencies for the parent of this index and append its path to it.
-        int parent = dependencies.get(index)[0];
-        String parentDependencies =
-                getParentDependencies(parent, paths, dependencies, splitDependencies);
-        String path = parentDependencies == null ? paths.get(parent) :
-                parentDependencies + ":" + paths.get(parent);
-        splitDependencies[index] = path;
-        return path;
-    }
-
-    /**
      * Checks if there is an update on the profile information of the {@code pkg}.
      * If the compiler filter is not profile guided the method returns false.
      *
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index da1df78..6b8a415 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -280,6 +280,8 @@
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
@@ -559,8 +561,9 @@
     public static final int REASON_INSTALL = 2;
     public static final int REASON_BACKGROUND_DEXOPT = 3;
     public static final int REASON_AB_OTA = 4;
+    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5;
 
-    public static final int REASON_LAST = REASON_AB_OTA;
+    public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE;
 
     /** All dangerous permission names in the same order as the events in MetricsEvent */
     private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
@@ -9392,24 +9395,42 @@
             // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
             // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
             // trade-off worth doing to save boot time work.
-            int dexOptStatus = performDexOptTraced(pkg.packageName,
-                    false /* checkProfiles */,
+            int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
+            int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
+                    pkg.packageName,
                     compilerFilter,
-                    false /* force */,
-                    bootComplete);
-            switch (dexOptStatus) {
-                case PackageDexOptimizer.DEX_OPT_PERFORMED:
-                    numberOfPackagesOptimized++;
-                    break;
-                case PackageDexOptimizer.DEX_OPT_SKIPPED:
-                    numberOfPackagesSkipped++;
-                    break;
-                case PackageDexOptimizer.DEX_OPT_FAILED:
-                    numberOfPackagesFailed++;
-                    break;
-                default:
-                    Log.e(TAG, "Unexpected dexopt return code " + dexOptStatus);
-                    break;
+                    dexoptFlags));
+
+            boolean secondaryDexOptStatus = true;
+            if (pkg.isSystemApp()) {
+                // Only dexopt shared secondary dex files belonging to system apps to not slow down
+                // too much boot after an OTA.
+                int secondaryDexoptFlags = dexoptFlags |
+                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                        DexoptOptions.DEXOPT_ONLY_SHARED_DEX;
+                mDexManager.dexoptSecondaryDex(new DexoptOptions(
+                        pkg.packageName,
+                        compilerFilter,
+                        secondaryDexoptFlags));
+            }
+
+            if (secondaryDexOptStatus) {
+                switch (primaryDexOptStaus) {
+                    case PackageDexOptimizer.DEX_OPT_PERFORMED:
+                        numberOfPackagesOptimized++;
+                        break;
+                    case PackageDexOptimizer.DEX_OPT_SKIPPED:
+                        numberOfPackagesSkipped++;
+                        break;
+                    case PackageDexOptimizer.DEX_OPT_FAILED:
+                        numberOfPackagesFailed++;
+                        break;
+                    default:
+                        Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStaus);
+                        break;
+                }
+            } else {
+                numberOfPackagesFailed++;
             }
         }
 
@@ -9476,17 +9497,53 @@
         }
     }
 
+    /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
     @Override
-    public boolean performDexOpt(String packageName,
-            boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) {
+    public boolean performDexOptMode(String packageName,
+            boolean checkProfiles, String targetCompilerFilter, boolean force,
+            boolean bootComplete, String splitName) {
+        int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) |
+                (force ? DexoptOptions.DEXOPT_FORCE : 0) |
+                (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0);
+        return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter,
+                splitName, flags));
+    }
+
+    /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter on the
+     * secondary dex files belonging to the given package.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
+    @Override
+    public boolean performDexOptSecondary(String packageName, String compilerFilter,
+            boolean force) {
+        int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                (force ? DexoptOptions.DEXOPT_FORCE : 0);
+        return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags));
+    }
+
+    /*package*/ boolean performDexOpt(DexoptOptions options) {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
+        } else if (isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
             return false;
         }
-        int dexoptStatus = performDexOptWithStatus(
-              packageName, checkProfiles, compileReason, force, bootComplete);
-        return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+
+        if (options.isDexoptOnlySecondaryDex()) {
+            return mDexManager.dexoptSecondaryDex(options);
+        } else {
+            int dexoptStatus = performDexOptWithStatus(options);
+            return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
+        }
     }
 
     /**
@@ -9495,33 +9552,14 @@
      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
-    /* package */ int performDexOptWithStatus(String packageName,
-            boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) {
-        return performDexOptTraced(packageName, checkProfiles,
-                getCompilerFilterForReason(compileReason), force, bootComplete);
+    /* package */ int performDexOptWithStatus(DexoptOptions options) {
+        return performDexOptTraced(options);
     }
 
-    @Override
-    public boolean performDexOptMode(String packageName,
-            boolean checkProfiles, String targetCompilerFilter, boolean force,
-            boolean bootComplete) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
-            return false;
-        }
-        int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
-                targetCompilerFilter, force, bootComplete);
-        return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
-    }
-
-    private int performDexOptTraced(String packageName,
-                boolean checkProfiles, String targetCompilerFilter, boolean force,
-                boolean bootComplete) {
+    private int performDexOptTraced(DexoptOptions options) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
         try {
-            return performDexOptInternal(packageName, checkProfiles,
-                    targetCompilerFilter, force, bootComplete);
+            return performDexOptInternal(options);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -9529,12 +9567,10 @@
 
     // Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
     // if the package can now be considered up to date for the given filter.
-    private int performDexOptInternal(String packageName,
-                boolean checkProfiles, String targetCompilerFilter, boolean force,
-                boolean bootComplete) {
+    private int performDexOptInternal(DexoptOptions options) {
         PackageParser.Package p;
         synchronized (mPackages) {
-            p = mPackages.get(packageName);
+            p = mPackages.get(options.getPackageName());
             if (p == null) {
                 // Package could not be found. Report failure.
                 return PackageDexOptimizer.DEX_OPT_FAILED;
@@ -9545,8 +9581,7 @@
         long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (mInstallLock) {
-                return performDexOptInternalWithDependenciesLI(p, checkProfiles,
-                        targetCompilerFilter, force, bootComplete);
+                return performDexOptInternalWithDependenciesLI(p, options);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -9566,12 +9601,11 @@
     }
 
     private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
-            boolean checkProfiles, String targetCompilerFilter,
-            boolean force, boolean bootComplete) {
+            DexoptOptions options) {
         // Select the dex optimizer based on the force parameter.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
-        PackageDexOptimizer pdo = force
+        PackageDexOptimizer pdo = options.isForce()
                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
                 : mPackageDexOptimizer;
 
@@ -9588,36 +9622,14 @@
             for (PackageParser.Package depPackage : deps) {
                 // TODO: Analyze and investigate if we (should) profile libraries.
                 pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
-                        false /* checkProfiles */,
-                        targetCompilerFilter,
                         getOrCreateCompilerPackageStats(depPackage),
                         true /* isUsedByOtherApps */,
-                        bootComplete);
+                        options);
             }
         }
-        return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
-                targetCompilerFilter, getOrCreateCompilerPackageStats(p),
-                mDexManager.isUsedByOtherApps(p.packageName), bootComplete);
-    }
-
-    // Performs dexopt on the used secondary dex files belonging to the given package.
-    // Returns true if all dex files were process successfully (which could mean either dexopt or
-    // skip). Returns false if any of the files caused errors.
-    @Override
-    public boolean performDexOptSecondary(String packageName, String compilerFilter,
-            boolean force) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return false;
-        } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
-            return false;
-        }
-        mDexManager.reconcileSecondaryDexFiles(packageName);
-        return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
-    }
-
-    public boolean performDexOptSecondary(String packageName, int compileReason,
-            boolean force) {
-        return mDexManager.dexoptSecondaryDex(packageName, compileReason, force);
+        return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets,
+                getOrCreateCompilerPackageStats(p),
+                mDexManager.isUsedByOtherApps(p.packageName), options);
     }
 
     /**
@@ -9793,10 +9805,11 @@
 
             // Whoever is calling forceDexOpt wants a compiled package.
             // Don't use profiles since that may cause compilation to be skipped.
-            final int res = performDexOptInternalWithDependenciesLI(pkg,
-                    false /* checkProfiles */, getDefaultCompilerFilter(),
-                    true /* force */,
-                    true /* bootComplete */);
+            final int res = performDexOptInternalWithDependenciesLI(
+                    pkg,
+                    new DexoptOptions(packageName,
+                            getDefaultCompilerFilter(),
+                            DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -16227,7 +16240,7 @@
         }
     }
 
-    private void removeDexFiles(List<String> allCodePaths, String[] instructionSets) {
+    void removeDexFiles(List<String> allCodePaths, String[] instructionSets) {
         if (!allCodePaths.isEmpty()) {
             if (instructionSets == null) {
                 throw new IllegalStateException("instructionSet == null");
@@ -18260,12 +18273,14 @@
                 // method because `pkg` may not be in `mPackages` yet.
                 //
                 // Also, don't fail application installs if the dexopt step fails.
+                DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName,
+                        REASON_INSTALL,
+                        DexoptOptions.DEXOPT_BOOT_COMPLETE);
                 mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
-                        null /* instructionSets */, false /* checkProfiles */,
-                        getCompilerFilterForReason(REASON_INSTALL),
+                        null /* instructionSets */,
                         getOrCreateCompilerPackageStats(pkg),
                         mDexManager.isUsedByOtherApps(pkg.packageName),
-                        true /* bootComplete */);
+                        dexoptOptions);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
 
@@ -25039,6 +25054,29 @@
             }
         }
     }
+
+    Set<String> getUnusedPackages(long downgradeTimeThresholdMillis) {
+        Set<String> unusedPackages = new HashSet<>();
+        long currentTimeInMillis = System.currentTimeMillis();
+        synchronized (mPackages) {
+            for (PackageParser.Package pkg : mPackages.values()) {
+                PackageSetting ps =  mSettings.mPackages.get(pkg.packageName);
+                if (ps == null) {
+                    continue;
+                }
+                PackageDexUsage.PackageUseInfo packageUseInfo = getDexManager().getPackageUseInfo(
+                        pkg.packageName);
+                if (PackageManagerServiceUtils
+                        .isUnusedSinceTimeInMillis(ps.firstInstallTime, currentTimeInMillis,
+                                downgradeTimeThresholdMillis, packageUseInfo,
+                                pkg.getLatestPackageUseTimeInMills(),
+                                pkg.getLatestForegroundPackageUseTimeInMills())) {
+                    unusedPackages.add(pkg.packageName);
+                }
+            }
+        }
+        return unusedPackages;
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index ec248f5..1a97a72 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -26,7 +26,7 @@
 public class PackageManagerServiceCompilerMapping {
     // Names for compilation reasons.
     static final String REASON_STRINGS[] = {
-            "first-boot", "boot", "install", "bg-dexopt", "ab-ota"
+            "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive"
     };
 
     // Static block to ensure the strings array is of the right length.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index eb1e7bd..820b2cb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.server.pm;
 
+import com.android.server.pm.dex.DexManager;
+import com.android.server.pm.dex.PackageDexUsage;
+
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.TAG;
 
@@ -24,6 +27,7 @@
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
@@ -186,6 +190,41 @@
     }
 
     /**
+     * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
+     * Package is considered active, if:
+     * 1) It was active in foreground.
+     * 2) It was active in background and also used by other apps.
+     *
+     * If it doesn't have sufficient information about the package, it return <code>false</code>.
+     */
+    static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
+            long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
+            long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
+
+        if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
+            return false;
+        }
+
+        // If the app was active in foreground during the threshold period.
+        boolean isActiveInForeground = (currentTimeInMillis
+                - latestForegroundPackageUseTimeInMillis)
+                < thresholdTimeinMillis;
+
+        if (isActiveInForeground) {
+            return false;
+        }
+
+        // If the app was active in background during the threshold period and was used
+        // by other packages.
+        boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
+                - latestPackageUseTimeInMillis)
+                < thresholdTimeinMillis)
+                && packageUseInfo.isUsedByOtherApps();
+
+        return !isActiveInBackgroundAndUsedByOtherPackages;
+    }
+
+    /**
      * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
      * semantics.
      */
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 20d7b28..10ceba4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -365,6 +365,7 @@
         String compilationReason = null;
         String checkProfilesRaw = null;
         boolean secondaryDex = false;
+        String split = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -395,6 +396,9 @@
                 case "--secondary-dex":
                     secondaryDex = true;
                     break;
+                case "--split":
+                    split = getNextArgRequired();
+                    break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
                     return 1;
@@ -423,6 +427,16 @@
             return 1;
         }
 
+        if (allPackages && split != null) {
+            pw.println("-a cannot be specified together with --split");
+            return 1;
+        }
+
+        if (secondaryDex && split != null) {
+            pw.println("--secondary-dex cannot be specified together with --split");
+            return 1;
+        }
+
         String targetCompilerFilter;
         if (compilerFilter != null) {
             if (!DexFile.isValidCompilerFilter(compilerFilter)) {
@@ -472,7 +486,7 @@
                             targetCompilerFilter, forceCompilation)
                     : mInterface.performDexOptMode(packageName,
                             checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */);
+                            true /* bootComplete */, split);
             if (!result) {
                 failedPackages.add(packageName);
             }
@@ -1609,7 +1623,7 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
-        pw.println("  compile [-m MODE | -r REASON] [-f] [-c]");
+        pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
         pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
         pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
         pw.println("    Options:");
@@ -1635,6 +1649,7 @@
         pw.println("      --reset: restore package to its post-install state");
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
         pw.println("      --secondary-dex: compile app secondary dex files");
+        pw.println("      --split SPLIT: compile only the given split name");
         pw.println("  bg-dexopt-job");
         pw.println("    Execute the background optimizations immediately.");
         pw.println("    Note that the command only runs the background optimizer logic. It may");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index be50eee..0d4df4d 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -300,31 +300,21 @@
     }
 
     /**
-     * Perform dexopt on the package {@code packageName} secondary dex files.
+     * Perform dexopt on with the given {@code options} on the secondary dex files.
      * @return true if all secondary dex files were processed successfully (compiled or skipped
      *         because they don't need to be compiled)..
      */
-    public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) {
-        return dexoptSecondaryDex(packageName,
-                PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason),
-                force);
-    }
-
-    /**
-     * Perform dexopt on the package {@code packageName} secondary dex files.
-     * @return true if all secondary dex files were processed successfully (compiled or skipped
-     *         because they don't need to be compiled)..
-     */
-    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
+    public boolean dexoptSecondaryDex(DexoptOptions options) {
         // Select the dex optimizer based on the force parameter.
         // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
         // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
         // passing the force flag through the multitude of layers.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
-        PackageDexOptimizer pdo = force
+        PackageDexOptimizer pdo = options.isForce()
                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
                 : mPackageDexOptimizer;
+        String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfo(packageName);
         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
@@ -337,6 +327,9 @@
         for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
             String dexPath = entry.getKey();
             DexUseInfo dexUseInfo = entry.getValue();
+            if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
+                continue;
+            }
             PackageInfo pkg = null;
             try {
                 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
@@ -356,7 +349,8 @@
             }
 
             int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
-                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
+                    dexUseInfo.getLoaderIsas(), options.getCompilerFilter(),
+                    dexUseInfo.isUsedByOtherApps(), options.isDowngrade());
             success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
         }
         return success;
@@ -472,7 +466,7 @@
         String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
                 PackageManagerService.REASON_INSTALL);
         int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas,
-                compilerFilter, isUsedByOtherApps);
+                compilerFilter, isUsedByOtherApps, /* downgrade */ false);
 
         // If we fail to optimize the package log an error but don't propagate the error
         // back to the app. The app cannot do much about it and the background job
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
new file mode 100644
index 0000000..f57cf5e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+
+import android.annotation.Nullable;
+
+/**
+ * Options used for dexopt invocations.
+ */
+public final class DexoptOptions {
+    // When set, the profiles will be checked for updates before calling dexopt. If
+    // the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt
+    // will be skipped.
+    // Currently this only affects the optimization of primary apks. Secondary dex files
+    // will always check the profiles for updates.
+    public static final int DEXOPT_CHECK_FOR_PROFILES_UPDATES = 1 << 0;
+
+    // When set, dexopt will execute unconditionally (even if not needed).
+    public static final int DEXOPT_FORCE = 1 << 1;
+
+    // Whether or not the invocation of dexopt is done after the boot is completed. This is used
+    // in order to adjust the priority of the compilation thread.
+    public static final int DEXOPT_BOOT_COMPLETE = 1 << 2;
+
+    // When set, the dexopt invocation will optimize only the secondary dex files. If false, dexopt
+    // will only consider the primary apk.
+    public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3;
+
+    // When set, dexopt will optimize only dex files that are used by other apps.
+    // Currently, this flag is ignored for primary apks.
+    public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4;
+
+    // When set, dexopt will attempt to scale down the optimizations previously applied in order
+    // save disk space.
+    public static final int DEXOPT_DOWNGRADE = 1 << 5;
+
+    // The name of package to optimize.
+    private final String mPackageName;
+
+    // The intended target compiler filter. Note that dexopt might adjust the filter before the
+    // execution based on factors like: vmSafeMode and packageUsedByOtherApps.
+    private final String mCompilerFilter;
+
+    // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags.
+    private final int mFlags;
+
+    // When not null, dexopt will optimize only the split identified by this name.
+    // It only applies for primary apk and it's always null if mOnlySecondaryDex is true.
+    private final String mSplitName;
+
+    public DexoptOptions(String packageName, String compilerFilter, int flags) {
+        this(packageName, compilerFilter, /*splitName*/ null, flags);
+    }
+
+    public DexoptOptions(String packageName, int compilerReason, int flags) {
+        this(packageName, getCompilerFilterForReason(compilerReason), flags);
+    }
+
+    public DexoptOptions(String packageName, String compilerFilter, String splitName, int flags) {
+        int validityMask =
+                DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DEXOPT_FORCE |
+                DEXOPT_BOOT_COMPLETE |
+                DEXOPT_ONLY_SECONDARY_DEX |
+                DEXOPT_ONLY_SHARED_DEX |
+                DEXOPT_DOWNGRADE;
+        if ((flags & (~validityMask)) != 0) {
+            throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
+        }
+
+        mPackageName = packageName;
+        mCompilerFilter = compilerFilter;
+        mFlags = flags;
+        mSplitName = splitName;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public boolean isCheckForProfileUpdates() {
+        return (mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) != 0;
+    }
+
+    public String getCompilerFilter() {
+        return mCompilerFilter;
+    }
+
+    public boolean isForce() {
+        return (mFlags & DEXOPT_FORCE) != 0;
+    }
+
+    public boolean isBootComplete() {
+        return (mFlags & DEXOPT_BOOT_COMPLETE) != 0;
+    }
+
+    public boolean isDexoptOnlySecondaryDex() {
+        return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0;
+    }
+
+    public boolean isDexoptOnlySharedDex() {
+        return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0;
+    }
+
+    public boolean isDowngrade() {
+        return (mFlags & DEXOPT_DOWNGRADE) != 0;
+    }
+
+    public String getSplitName() {
+        return mSplitName;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
new file mode 100644
index 0000000..abac52f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.util.List;
+
+public final class DexoptUtils {
+    private static final String TAG = "DexoptUtils";
+
+    private DexoptUtils() {}
+
+    /**
+     * Creates the class loader context dependencies for each of the application code paths.
+     * The returned array contains the class loader contexts that needs to be passed to dexopt in
+     * order to ensure correct optimizations.
+     *
+     * A class loader context describes how the class loader chain should be built by dex2oat
+     * in order to ensure that classes are resolved during compilation as they would be resolved
+     * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is
+     * loaded in a different context (with a different set of class loaders or a different
+     * classpath), the compiled code will be rejected.
+     *
+     * Note that the class loader context only includes dependencies and not the code path itself.
+     * The contexts are created based on the application split dependency list and
+     * the provided shared libraries.
+     *
+     * All the code paths encoded in the context will be relative to the base directory. This
+     * enables stage compilation where compiler artifacts may be moved around.
+     *
+     * The result is indexed as follows:
+     *   - index 0 contains the context for the base apk
+     *   - index 1 to n contain the context for the splits in the order determined by
+     *     {@code info.getSplitCodePaths()}
+     *
+     * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
+     * and pay attention to the way the classpath is created for the non isolated mode in:
+     * {@link android.app.LoadedApk#makePaths(
+     * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
+     */
+    public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
+        // The base class loader context contains only the shared library.
+        String sharedLibrariesClassPath = encodeClasspath(sharedLibraries);
+        String baseApkContextClassLoader = encodeClassLoader(
+                sharedLibrariesClassPath, "dalvik.system.PathClassLoader");
+
+        if (info.getSplitCodePaths() == null) {
+            // The application has no splits.
+            return new String[] {baseApkContextClassLoader};
+        }
+
+        // The application has splits. Compute their class loader contexts.
+
+        // First, cache the relative paths of the splits and do some sanity checks
+        String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);
+
+        // The splits have an implicit dependency on the base apk.
+        // This means that we have to add the base apk file in addition to the shared libraries.
+        String baseApkName = new File(info.getBaseCodePath()).getName();
+        String sharedLibrariesAndBaseClassPath =
+                encodeClasspath(sharedLibrariesClassPath, baseApkName);
+
+        // The result is stored in classLoaderContexts.
+        // Index 0 is the class loaded context for the base apk.
+        // Index `i` is the class loader context encoding for split `i`.
+        String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
+        classLoaderContexts[0] = baseApkContextClassLoader;
+
+        if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
+            // If the app didn't request for the splits to be loaded in isolation or if it does not
+            // declare inter-split dependencies, then all the splits will be loaded in the base
+            // apk class loader (in the order of their definition).
+            String classpath = sharedLibrariesAndBaseClassPath;
+            for (int i = 1; i < classLoaderContexts.length; i++) {
+                classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
+                classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
+            }
+        } else {
+            // In case of inter-split dependencies, we need to walk the dependency chain of each
+            // split. We do this recursively and store intermediate results in classLoaderContexts.
+
+            // First, look at the split class loaders and cache their individual contexts (i.e.
+            // the class loader + the name of the split). This is an optimization to avoid
+            // re-computing them during the recursive call.
+            // The cache is stored in splitClassLoaderEncodingCache. The difference between this and
+            // classLoaderContexts is that the later contains the full chain of class loaders for
+            // a given split while splitClassLoaderEncodingCache only contains a single class loader
+            // encoding.
+            String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
+            for (int i = 0; i < splitRelativeCodePaths.length; i++) {
+                splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
+                        "dalvik.system.PathClassLoader");
+            }
+            String splitDependencyOnBase = encodeClassLoader(
+                    sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
+            SparseArray<int[]> splitDependencies = info.splitDependencies;
+            for (int i = 1; i < splitDependencies.size(); i++) {
+                getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
+                        splitDependencies, classLoaderContexts, splitDependencyOnBase);
+            }
+
+            // At this point classLoaderContexts contains only the parent dependencies.
+            // We also need to add the class loader of the current split which should
+            // come first in the context.
+            for (int i = 1; i < classLoaderContexts.length; i++) {
+                String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
+                classLoaderContexts[i] = encodeClassLoaderChain(
+                        splitClassLoader, classLoaderContexts[i]);
+            }
+        }
+
+        return classLoaderContexts;
+    }
+
+    /**
+     * Recursive method to generate the class loader context dependencies for the split with the
+     * given index. {@param classLoaderContexts} acts as an accumulator. Upton return
+     * {@code classLoaderContexts[index]} will contain the split dependency.
+     * During computation, the method may resolve the dependencies of other splits as it traverses
+     * the entire parent chain. The result will also be stored in {@param classLoaderContexts}.
+     *
+     * Note that {@code index 0} denotes the base apk and it is special handled. When the
+     * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}.
+     * {@code classLoaderContexts[0]} is not modified in this method.
+     *
+     * @param index the index of the split (Note that index 0 denotes the base apk)
+     * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits.
+     *    It contains only the split class loader and not the the base. The split
+     *    with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}.
+     * @param splitDependencies the dependencies for all splits. Note that in this array index 0
+     *    is the base and splits start from index 1.
+     * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits
+     *    start at index 1.
+     * @param splitDependencyOnBase the encoding of the implicit split dependency on base.
+     */
+    private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache,
+            SparseArray<int[]> splitDependencies, String[] classLoaderContexts,
+            String splitDependencyOnBase) {
+        // If we hit the base apk return its custom dependency list which is
+        // sharedLibraries + base.apk
+        if (index == 0) {
+            return splitDependencyOnBase;
+        }
+        // Return the result if we've computed the splitDependencies for this index already.
+        if (classLoaderContexts[index] != null) {
+            return classLoaderContexts[index];
+        }
+        // Get the splitDependencies for the parent of this index and append its path to it.
+        int parent = splitDependencies.get(index)[0];
+        String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache,
+                splitDependencies, classLoaderContexts, splitDependencyOnBase);
+
+        // The split context is: `parent context + parent dependencies context`.
+        String splitContext = (parent == 0) ?
+                parentDependencies :
+                encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies);
+        classLoaderContexts[index] = splitContext;
+        return splitContext;
+    }
+
+    /**
+     * Encodes the shared libraries classpathElements in a format accepted by dexopt.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is
+     * a list separated by ':'.
+     */
+    private static String encodeClasspath(String[] classpathElements) {
+        if (classpathElements == null || classpathElements.length == 0) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String element : classpathElements) {
+            if (sb.length() != 0) {
+                sb.append(":");
+            }
+            sb.append(element);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Adds an element to the encoding of an existing classpath.
+     * {@see PackageDexOptimizer.encodeClasspath(String[])}
+     */
+    private static String encodeClasspath(String classpath, String newElement) {
+        return classpath.isEmpty() ? newElement : (classpath + ":" + newElement);
+    }
+
+    /**
+     * Encodes a single class loader dependency starting from {@param path} and
+     * {@param classLoaderName}.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
+     * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
+     */
+    private static String encodeClassLoader(String classpath, String classLoaderName) {
+        String classLoaderDexoptEncoding = classLoaderName;
+        if ("dalvik.system.PathClassLoader".equals(classLoaderName)) {
+            classLoaderDexoptEncoding = "PCL";
+        } else {
+            Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName);
+        }
+        return classLoaderDexoptEncoding + "[" + classpath + "]";
+    }
+
+    /**
+     * Links to dependencies together in a format accepted by dexopt.
+     * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
+     * dependencies {@see encodeClassLoader} separated by ';'.
+     */
+    private static String encodeClassLoaderChain(String cl1, String cl2) {
+        return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2);
+    }
+
+    /**
+     * Returns the relative paths of the splits declared by the application {@code info}.
+     * Assumes that the application declares a non-null array of splits.
+     */
+    private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
+        String baseCodePath = new File(info.getBaseCodePath()).getParent();
+        String[] splitCodePaths = info.getSplitCodePaths();
+        String[] splitRelativeCodePaths = new String[splitCodePaths.length];
+        for (int i = 0; i < splitCodePaths.length; i++) {
+            File pathFile = new File(splitCodePaths[i]);
+            splitRelativeCodePaths[i] = pathFile.getName();
+            // Sanity check that the base paths of the splits are all the same.
+            String basePath = pathFile.getParent();
+            if (!basePath.equals(baseCodePath)) {
+                Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
+                        baseCodePath);
+            }
+        }
+        return splitRelativeCodePaths;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
new file mode 100644
index 0000000..1eb5552
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+
+import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DexoptOptionsTests {
+    private final static String mPackageName = "test.android.com";
+    private final static String mCompilerFilter =
+            PackageManagerServiceCompilerMapping.getDefaultCompilerFilter();
+    private final static String mSplitName = "split-A.apk";
+
+    @Test
+    public void testCreateDexoptOptionsEmpty() {
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, /*flags*/ 0);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(null, opt.getSplitName());
+        assertFalse(opt.isBootComplete());
+        assertFalse(opt.isCheckForProfileUpdates());
+        assertFalse(opt.isDexoptOnlySecondaryDex());
+        assertFalse(opt.isDexoptOnlySharedDex());
+        assertFalse(opt.isDowngrade());
+        assertFalse(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptOptionsFull() {
+        int flags =
+                DexoptOptions.DEXOPT_FORCE |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
+                DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
+                DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
+                DexoptOptions.DEXOPT_DOWNGRADE;
+
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(null, opt.getSplitName());
+        assertTrue(opt.isBootComplete());
+        assertTrue(opt.isCheckForProfileUpdates());
+        assertTrue(opt.isDexoptOnlySecondaryDex());
+        assertTrue(opt.isDexoptOnlySharedDex());
+        assertTrue(opt.isDowngrade());
+        assertTrue(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptOptionsReason() {
+        int flags =
+                DexoptOptions.DEXOPT_FORCE |
+                DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
+
+        int[] reasons = new int[] {
+                PackageManagerService.REASON_FIRST_BOOT,
+                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_INSTALL,
+                PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                PackageManagerService.REASON_AB_OTA,
+                PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE};
+
+        for (int reason : reasons) {
+            DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags);
+            assertEquals(mPackageName, opt.getPackageName());
+            assertEquals(getCompilerFilterForReason(reason), opt.getCompilerFilter());
+            assertEquals(null, opt.getSplitName());
+            assertTrue(opt.isBootComplete());
+            assertTrue(opt.isCheckForProfileUpdates());
+            assertFalse(opt.isDexoptOnlySecondaryDex());
+            assertFalse(opt.isDexoptOnlySharedDex());
+            assertFalse(opt.isDowngrade());
+            assertTrue(opt.isForce());
+        }
+    }
+
+    @Test
+    public void testCreateDexoptOptionsSplit() {
+        int flags = DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE;
+
+        DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, mSplitName, flags);
+        assertEquals(mPackageName, opt.getPackageName());
+        assertEquals(mCompilerFilter, opt.getCompilerFilter());
+        assertEquals(mSplitName, opt.getSplitName());
+        assertTrue(opt.isBootComplete());
+        assertFalse(opt.isCheckForProfileUpdates());
+        assertFalse(opt.isDexoptOnlySecondaryDex());
+        assertFalse(opt.isDexoptOnlySharedDex());
+        assertFalse(opt.isDowngrade());
+        assertTrue(opt.isForce());
+    }
+
+    @Test
+    public void testCreateDexoptInvalid() {
+        boolean gotException = false;
+        try {
+            int invalidFlags = 999;
+            new DexoptOptions(mPackageName, mCompilerFilter, invalidFlags);
+        } catch (IllegalArgumentException ignore) {
+            gotException = true;
+        }
+
+        assertTrue(gotException);
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
new file mode 100644
index 0000000..21b286e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.ApplicationInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import dalvik.system.DelegateLastClassLoader;
+import dalvik.system.PathClassLoader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DexoptUtilsTest {
+    private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
+    private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
+            DelegateLastClassLoader.class.getName();
+
+    private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits,
+            boolean addSplitDependencies) {
+        ApplicationInfo ai = new ApplicationInfo();
+        String codeDir = "/data/app/mock.android.com";
+        ai.setBaseCodePath(codeDir + "/base.dex");
+        ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+
+        if (addSplits) {
+            ai.setSplitCodePaths(new String[]{
+                    codeDir + "/base-1.dex",
+                    codeDir + "/base-2.dex",
+                    codeDir + "/base-3.dex",
+                    codeDir + "/base-4.dex",
+                    codeDir + "/base-5.dex",
+                    codeDir + "/base-6.dex"});
+
+            if (addSplitDependencies) {
+                ai.splitDependencies = new SparseArray<>(6 + 1);
+                ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency
+                ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2
+                ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4
+                ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4
+                ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base
+                ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base
+                ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5
+            }
+        }
+        return ai;
+    }
+
+    @Test
+    public void testSplitChain() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
+                contexts[1]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void testSplitChainNoSplitDependencies() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+        assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]);
+        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+        assertEquals(
+                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]",
+                contexts[5]);
+        assertEquals(
+                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+                contexts[6]);
+    }
+
+    @Test
+    public void testSplitChainNoIsolationNoSharedLibrary() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+        assertEquals("PCL[base.dex]", contexts[1]);
+        assertEquals("PCL[base.dex:base-1.dex]", contexts[2]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]);
+        assertEquals(
+                "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+                contexts[6]);
+    }
+    @Test
+    public void testSplitChainNoSharedLibraries() {
+        ApplicationInfo ai = createMockApplicationInfo(
+                DELEGATE_LAST_CLASS_LOADER_NAME, true, true);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[3]);
+        assertEquals("PCL[];PCL[base.dex]", contexts[4]);
+        assertEquals("PCL[];PCL[base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void testSplitChainWithNullPrimaryClassLoader() {
+        // A null classLoaderName should mean PathClassLoader.
+        ApplicationInfo ai = createMockApplicationInfo(null, true, true);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(7, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+        assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+    }
+
+    @Test
+    public void tesNoSplits() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitsNullClassLoaderName() {
+        ApplicationInfo ai = createMockApplicationInfo(null, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitDelegateLast() {
+        ApplicationInfo ai = createMockApplicationInfo(
+                DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitsNoSharedLibraries() {
+        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+    }
+
+    @Test
+    public void tesNoSplitDelegateLastNoSharedLibraries() {
+        ApplicationInfo ai = createMockApplicationInfo(
+                DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+        assertEquals(1, contexts.length);
+        assertEquals("PCL[]", contexts[0]);
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 9f4fb85..21b11b0 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -392,6 +392,7 @@
         res.videoBytes = stats[2];
         res.imageBytes = stats[3];
         res.appBytes = stats[4];
+        res.obbBytes = stats[5];
         return res;
     }