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;
}