summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp43
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/app/SystemServiceRegistry.java16
-rw-r--r--core/java/android/content/Context.java25
-rw-r--r--core/java/android/content/ContextWrapper.java10
-rw-r--r--core/java/android/os/IVibratorService.aidl1
-rw-r--r--core/java/android/os/Process.java15
-rw-r--r--core/java/android/os/SystemVibrator.java14
-rw-r--r--core/java/android/os/Vibrator.java19
-rw-r--r--core/java/android/os/ZygoteProcess.java35
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/view/FrameMetricsObserver.java61
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/com/android/internal/os/Zygote.java26
-rw-r--r--core/java/com/android/internal/os/ZygoteArguments.java8
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java2
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android/graphics/apex/jni_runtime.cpp4
-rw-r--r--core/jni/android_graphics_HardwareRendererObserver.cpp130
-rw-r--r--core/jni/android_graphics_HardwareRendererObserver.h75
-rw-r--r--core/jni/android_view_FrameMetricsObserver.cpp149
-rw-r--r--core/jni/android_view_FrameMetricsObserver.h71
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp29
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp307
-rw-r--r--core/proto/android/app/settings_enums.proto27
-rw-r--r--core/proto/android/stats/docsui/docsui_enums.proto1
-rw-r--r--core/proto/android/util/quotatracker.proto219
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--core/res/res/layout/chooser_grid_preview_text.xml3
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java18
-rw-r--r--graphics/java/android/graphics/HardwareRendererObserver.java103
-rw-r--r--media/java/android/media/MediaMetrics.java634
-rw-r--r--media/java/android/media/MediaRouter2.java210
-rw-r--r--media/java/android/media/MediaRouter2Manager.java86
-rw-r--r--media/java/android/media/RouteSessionController.java208
-rw-r--r--media/jni/android_media_MediaMetricsJNI.cpp38
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java116
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java150
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java27
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java40
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--services/Android.bp3
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java27
-rw-r--r--services/core/java/com/android/server/VibratorService.java63
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java66
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricServiceBase.java13
-rw-r--r--services/core/java/com/android/server/integrity/OWNERS1
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java117
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java67
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java127
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java8
-rw-r--r--services/core/java/com/android/server/pm/Installer.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java12
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java16
-rw-r--r--services/core/java/com/android/server/utils/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/utils/quota/UptcMap.java163
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java25
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java33
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp17
-rw-r--r--services/java/com/android/server/SystemServer.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java (renamed from services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java)37
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java29
-rw-r--r--test-mock/Android.bp2
-rw-r--r--test-mock/src/android/test/mock/MockContext.java10
70 files changed, 3114 insertions, 771 deletions
diff --git a/Android.bp b/Android.bp
index 15188ece0a6d..77abad6e0fd9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -799,11 +799,7 @@ cc_library {
filegroup {
name: "incremental_aidl",
srcs: [
- "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
- "core/java/android/os/incremental/IIncrementalManager.aidl",
- "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
- "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
],
path: "core/java",
}
@@ -812,6 +808,17 @@ filegroup {
name: "dataloader_aidl",
srcs: [
"core/java/android/content/pm/IDataLoaderStatusListener.aidl",
+ "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
+ "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
+ ],
+ path: "core/java",
+}
+
+filegroup {
+ name: "incremental_manager_aidl",
+ srcs: [
+ "core/java/android/os/incremental/IIncrementalManager.aidl",
+ "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
],
path: "core/java",
}
@@ -821,9 +828,6 @@ aidl_interface {
srcs: [
":incremental_aidl",
],
- imports: [
- "libdataloader_aidl",
- ],
backend: {
java: {
sdk_version: "28",
@@ -842,6 +846,9 @@ aidl_interface {
srcs: [
":dataloader_aidl",
],
+ imports: [
+ "libincremental_aidl",
+ ],
backend: {
java: {
sdk_version: "28",
@@ -850,8 +857,30 @@ aidl_interface {
enabled: true,
},
ndk: {
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "libincremental_manager_aidl",
+ srcs: [
+ ":incremental_manager_aidl",
+ ],
+ imports: [
+ "libincremental_aidl",
+ "libdataloader_aidl",
+ ],
+ backend: {
+ java: {
+ sdk_version: "28",
+ },
+ cpp: {
enabled: true,
},
+ ndk: {
+ enabled: false,
+ },
},
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 5381376d7568..219258ef50b0 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -697,6 +697,7 @@ package android.content {
public abstract class Context {
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public java.io.File getCrateDir(@NonNull String);
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
method public android.os.UserHandle getUser();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 46f88d5c81e4..155e93f9be19 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,6 +99,7 @@ import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteOrder;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -251,6 +252,8 @@ class ContextImpl extends Context {
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mCratesDir;
+ @GuardedBy("mSync")
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
@@ -702,6 +705,24 @@ class ContextImpl extends Context {
}
@Override
+ public File getCrateDir(@NonNull String crateId) {
+ Preconditions.checkArgument(FileUtils.isValidExtFilename(crateId), "invalidated crateId");
+ final Path cratesRootPath = getDataDir().toPath().resolve("crates");
+ final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
+ .toAbsolutePath().normalize();
+
+ synchronized (mSync) {
+ if (mCratesDir == null) {
+ mCratesDir = cratesRootPath.toFile();
+ }
+ ensurePrivateDirExists(mCratesDir);
+ }
+
+ File cratedDir = absoluteNormalizedCratePath.toFile();
+ return ensurePrivateDirExists(cratedDir);
+ }
+
+ @Override
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a1305da0a502..ce21db335615 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,6 +152,8 @@ import android.os.Vibrator;
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
+import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
@@ -1225,6 +1227,20 @@ public final class SystemServiceRegistry {
Context.DATA_LOADER_MANAGER_SERVICE);
return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
}});
+ //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
+ //IIncrementalManagerNative.aidl, 2) implement the binder interface in
+ //IncrementalManagerService.java, 3) use JNI to call native functions
+ registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
+ new CachedServiceFetcher<IncrementalManager>() {
+ @Override
+ public IncrementalManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+ if (b == null) {
+ return null;
+ }
+ return new IncrementalManager(
+ IIncrementalManagerNative.Stub.asInterface(b));
+ }});
//CHECKSTYLE:ON IndentationCheck
sInitializing = true;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d0844491da53..24b5061bf27b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1060,6 +1060,31 @@ public abstract class Context {
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory that is related to the crate on the filesystem.
+ * <p>
+ * The crateId require a validated file name. It can't contain any "..", ".",
+ * {@link File#separatorChar} etc..
+ * </p>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * </p>
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *</p>
+ *
+ * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+ * @return the crate directory file.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ public File getCrateDir(@NonNull String crateId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Returns the absolute path to the directory on the filesystem similar to
* {@link #getFilesDir()}. The difference is that files placed under this
* directory will be excluded from automatic backup to remote storage. See
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index d6442e28439f..d1b5135e959d 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -252,6 +252,16 @@ public class ContextWrapper extends Context {
return mBase.getFilesDir();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @NonNull
+ @Override
+ public File getCrateDir(@NonNull String cratedId) {
+ return mBase.getCrateDir(cratedId);
+ }
+
@Override
public File getNoBackupFilesDir() {
return mBase.getNoBackupFilesDir();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 1456ff7e6c5e..6b881fecad56 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,6 +24,7 @@ interface IVibratorService
{
boolean hasVibrator();
boolean hasAmplitudeControl();
+ boolean setAlwaysOnEffect(int id, in VibrationEffect effect, in AudioAttributes attributes);
void vibrate(int uid, String opPkg, in VibrationEffect effect, in AudioAttributes attributes,
String reason, IBinder token);
void cancelVibrate(IBinder token);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ebb2071ead7e..6ae188a8fc3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -22,10 +22,13 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Pair;
import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* Tools for managing OS processes.
*/
@@ -521,6 +524,8 @@ public class Process {
* @param isTopApp whether the process starts for high priority application.
* @param disabledCompatChanges null-ok list of disabled compat changes for the process being
* started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -541,11 +546,14 @@ public class Process {
@Nullable String packageName,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ true, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
}
/** @hide */
@@ -563,10 +571,13 @@ public class Process {
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
@Nullable String[] zygoteArgs) {
+ // Webview zygote can't access app private data files, so doesn't need to know its data
+ // info.
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges,
+ /* pkgDataInfoMap */ null, zygoteArgs);
}
/**
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index a5188e7cd58d..fbd11ca62a0c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,6 +70,20 @@ public class SystemVibrator extends Vibrator {
}
@Override
+ public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.setAlwaysOnEffect(id, effect, attributes);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
public void vibrate(int uid, String opPkg, VibrationEffect effect,
String reason, AudioAttributes attributes) {
if (mService == null) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 28909c88a734..6456d72a4a6f 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
@@ -152,6 +153,24 @@ public abstract class Vibrator {
public abstract boolean hasAmplitudeControl();
/**
+ * Configure an always-on haptics effect.
+ *
+ * @param id The board-specific always-on ID to configure.
+ * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
+ * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+ * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+ * vibrations associated with incoming calls. May only be null when effect is null.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
* Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index d17a5e026880..d32bd26c2bb2 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -39,6 +40,7 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/*package*/ class ZygoteStartFailedEx extends Exception {
@@ -310,6 +312,8 @@ public class ZygoteProcess {
* started.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @param isTopApp Whether the process starts for high priority application.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
*
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -328,6 +332,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -338,7 +344,8 @@ public class ZygoteProcess {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
- packageName, useUsapPool, isTopApp, disabledCompatChanges, zygoteArgs);
+ packageName, useUsapPool, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -539,6 +546,8 @@ public class ZygoteProcess {
* @param packageName null-ok the name of the package this process belongs to.
* @param isTopApp Whether the process starts for high priority application.
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
+ * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
+ * and inode number.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -559,6 +568,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -635,6 +646,24 @@ public class ZygoteProcess {
if (isTopApp) {
argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
}
+ if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.PKG_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
StringBuilder sb = new StringBuilder();
@@ -1182,12 +1211,14 @@ public class ZygoteProcess {
Process.ProcessStartResult result;
try {
+ // As app zygote is for generating isolated process, at the end it can't access
+ // apps data, so doesn't need to its data info.
result = startViaZygote(processClass, niceName, uid, gid,
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
false /* useUsapPool */, false /* isTopApp */,
- null /* disabledCompatChanges */, extraArgs);
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e73a74f69dee..f4e2329797b2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13653,13 +13653,6 @@ public final class Settings {
public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
/**
- * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
- *
- * @hide
- */
- public static final String DEFAULT_USER_ID_TO_BOOT_INTO = "default_boot_into_user_id";
-
- /**
* Persistent user id that is last logged in to.
*
* They map to user ids, for example, 10, 11, 12.
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
index 0f38e847f4bd..41bc9a742752 100644
--- a/core/java/android/view/FrameMetricsObserver.java
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -17,11 +17,8 @@
package android.view;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-import com.android.internal.util.VirtualRefBasePtr;
+import android.graphics.HardwareRendererObserver;
+import android.os.Handler;
import java.lang.ref.WeakReference;
@@ -31,47 +28,39 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-public class FrameMetricsObserver {
- @UnsupportedAppUsage
- private MessageQueue mMessageQueue;
-
- private WeakReference<Window> mWindow;
-
- @UnsupportedAppUsage
- private FrameMetrics mFrameMetrics;
-
- /* pacage */ Window.OnFrameMetricsAvailableListener mListener;
- /** @hide */
- public VirtualRefBasePtr mNative;
+public class FrameMetricsObserver
+ implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+ private final WeakReference<Window> mWindow;
+ private final FrameMetrics mFrameMetrics;
+ private final HardwareRendererObserver mObserver;
+ /*package*/ final Window.OnFrameMetricsAvailableListener mListener;
/**
* Creates a FrameMetricsObserver
*
- * @param looper the looper to use when invoking callbacks
+ * @param handler the Handler to use when invoking callbacks
*/
- FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+ FrameMetricsObserver(@NonNull Window window, @NonNull Handler handler,
@NonNull Window.OnFrameMetricsAvailableListener listener) {
- if (looper == null) {
- throw new NullPointerException("looper cannot be null");
- }
-
- mMessageQueue = looper.getQueue();
- if (mMessageQueue == null) {
- throw new IllegalStateException("invalid looper, null message queue\n");
- }
-
- mFrameMetrics = new FrameMetrics();
mWindow = new WeakReference<>(window);
mListener = listener;
+ mFrameMetrics = new FrameMetrics();
+ mObserver = new HardwareRendererObserver(this, mFrameMetrics.mTimingData, handler);
}
- // Called by native on the provided Handler
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private void notifyDataAvailable(int dropCount) {
- final Window window = mWindow.get();
- if (window != null) {
- mListener.onFrameMetricsAvailable(window, mFrameMetrics, dropCount);
+ /**
+ * Implementation of OnFrameMetricsAvailableListener
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * @Override
+ */
+ public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ if (mWindow.get() != null) {
+ mListener.onFrameMetricsAvailable(mWindow.get(), mFrameMetrics,
+ dropCountSinceLastInvocation);
}
}
+
+ /*package*/ HardwareRendererObserver getRendererObserver() {
+ return mObserver;
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 44f211ac8ede..1639dbe697d3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7147,10 +7147,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
- mAttachInfo.mThreadedRenderer.addFrameMetricsObserver(fmo);
+ mAttachInfo.mThreadedRenderer.addObserver(fmo.getRendererObserver());
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
}
@@ -7159,8 +7158,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
}
}
@@ -7182,7 +7180,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mFrameMetricsObservers != null) {
mFrameMetricsObservers.remove(fmo);
if (renderer != null) {
- renderer.removeFrameMetricsObserver(fmo);
+ renderer.removeObserver(fmo.getRendererObserver());
}
}
}
@@ -7192,7 +7190,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ThreadedRenderer renderer = getThreadedRenderer();
if (renderer != null) {
for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
- renderer.addFrameMetricsObserver(fmo);
+ renderer.addObserver(fmo.getRendererObserver());
}
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2b988c155412..c390a518bb22 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -151,6 +151,9 @@ public final class Zygote {
/** Make the new process have top application priority. */
public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+ /** List of packages with the same uid, and its app data info: volume uuid and inode. */
+ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -254,6 +257,8 @@ public final class Zygote {
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
* @param isTopApp true if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*
* @return 0 if this is the child, pid of the child
* if this is the parent, or -1 on error.
@@ -261,12 +266,13 @@ public final class Zygote {
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- int targetSdkVersion, boolean isTopApp) {
+ int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp);
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Zygote.disableExecuteOnly(targetSdkVersion);
@@ -286,7 +292,7 @@ public final class Zygote {
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, boolean isTopApp);
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
/**
* Specialize an unspecialized app process. The current VM must have been started
@@ -309,12 +315,16 @@ public final class Zygote {
* @param instructionSet null-ok The instruction set to use.
* @param appDataDir null-ok The data directory of the app.
* @param isTopApp True if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) {
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
- niceName, startChildZygote, instructionSet, appDataDir, isTopApp);
+ niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -336,7 +346,8 @@ public final class Zygote {
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp);
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList);
/**
* Called to do any initialization before starting an application.
@@ -665,7 +676,8 @@ public final class Zygote {
specializeAppProcess(args.mUid, args.mGid, args.mGids,
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
- args.mInstructionSet, args.mAppDataDir, args.mIsTopApp);
+ args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
+ args.mPkgDataInfoList);
disableExecuteOnly(args.mTargetSdkVersion);
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 54b2a2063451..d3499541a3a3 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -221,6 +221,12 @@ class ZygoteArguments {
long[] mDisabledCompatChanges = null;
/**
+ * A list that stores all related packages and its data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mPkgDataInfoList;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -437,6 +443,8 @@ class ZygoteArguments {
for (int i = 0; i < length; i++) {
mDisabledCompatChanges[i] = Long.parseLong(params[i]);
}
+ } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
+ mPkgDataInfoList = getAssignmentList(arg);
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3111b6ff0f72..9c6a288372c6 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@ class ZygoteConnection {
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
- parsedArgs.mIsTopApp);
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
try {
if (pid == 0) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a2f6a62157ed..60c5bf1529a6 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,7 +110,6 @@ cc_library_shared {
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_FrameMetricsObserver.cpp",
"android_view_KeyCharacterMap.cpp",
"android_view_KeyEvent.cpp",
"android_view_MotionEvent.cpp",
@@ -351,6 +350,7 @@ cc_library_static {
"android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
"android_graphics_drawable_VectorDrawable.cpp",
+ "android_graphics_HardwareRendererObserver.cpp",
"android_graphics_Picture.cpp",
"android_nio_utils.cpp",
"android_view_DisplayListCanvas.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f7a994f7071f..3cde887ba465 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -129,6 +129,7 @@ extern int register_android_database_CursorWindow(JNIEnv* env);
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -1520,6 +1521,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/jni/android/graphics/apex/jni_runtime.cpp b/core/jni/android/graphics/apex/jni_runtime.cpp
index 7f9bac0df44a..1f661534ad81 100644
--- a/core/jni/android/graphics/apex/jni_runtime.cpp
+++ b/core/jni/android/graphics/apex/jni_runtime.cpp
@@ -52,6 +52,7 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
@@ -71,7 +72,6 @@ extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
-extern int register_android_view_FrameMetricsObserver(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
@@ -105,6 +105,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_FontFamily),
+ REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
REG_JNI(register_android_graphics_Interpolator),
@@ -135,7 +136,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_FrameMetricsObserver),
REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
};
diff --git a/core/jni/android_graphics_HardwareRendererObserver.cpp b/core/jni/android_graphics_HardwareRendererObserver.cpp
new file mode 100644
index 000000000000..89b77b0b069a
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "android_graphics_HardwareRendererObserver.h"
+
+#include "core_jni_helpers.h"
+#include "nativehelper/jni_macros.h"
+
+#include <array>
+
+namespace android {
+
+struct {
+ jmethodID callback;
+} gHardwareRendererObserverClassInfo;
+
+static JNIEnv* getenv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+ }
+ return env;
+}
+
+HardwareRendererObserver::HardwareRendererObserver(JavaVM *vm, jobject observer) : mVm(vm) {
+ mObserverWeak = getenv(mVm)->NewWeakGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
+ "unable to create frame stats observer reference");
+}
+
+HardwareRendererObserver::~HardwareRendererObserver() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mObserverWeak);
+}
+
+bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount) {
+ jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(metrics));
+ LOG_ALWAYS_FATAL_IF(bufferSize != HardwareRendererObserver::kBufferSize,
+ "Mismatched Java/Native FrameMetrics data format.");
+
+ FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+ if (elem.hasData.load()) {
+ env->SetLongArrayRegion(metrics, 0, kBufferSize, elem.buffer);
+ *dropCount = elem.dropCount;
+ mNextInQueue = (mNextInQueue + 1) % kRingSize;
+ elem.hasData = false;
+ return true;
+ }
+
+ return false;
+}
+
+void HardwareRendererObserver::notify(const int64_t* stats) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+ if (!elem.hasData.load()) {
+ memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+ elem.dropCount = mDroppedReports;
+ mDroppedReports = 0;
+ mNextFree = (mNextFree + 1) % kRingSize;
+ elem.hasData = true;
+
+ JNIEnv* env = getenv(mVm);
+ jobject target = env->NewLocalRef(mObserverWeak);
+ if (target != nullptr) {
+ env->CallVoidMethod(target, gHardwareRendererObserverClassInfo.callback);
+ env->DeleteLocalRef(target);
+ }
+ } else {
+ mDroppedReports++;
+ }
+}
+
+static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* env,
+ jobject observerObj) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
+ }
+
+ HardwareRendererObserver* observer = new HardwareRendererObserver(vm, observerObj);
+ return reinterpret_cast<jlong>(observer);
+}
+
+static jint android_graphics_HardwareRendererObserver_getNextBuffer(JNIEnv* env, jobject,
+ jlong observerPtr,
+ jlongArray metrics) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
+ int dropCount = 0;
+ if (observer->getNextBuffer(env, metrics, &dropCount)) {
+ return dropCount;
+ } else {
+ return -1;
+ }
+}
+
+static const std::array gMethods = {
+ MAKE_JNI_NATIVE_METHOD("nCreateObserver", "()J",
+ android_graphics_HardwareRendererObserver_createObserver),
+ MAKE_JNI_NATIVE_METHOD("nGetNextBuffer", "(J[J)I",
+ android_graphics_HardwareRendererObserver_getNextBuffer),
+};
+
+int register_android_graphics_HardwareRendererObserver(JNIEnv* env) {
+
+ jclass observerClass = FindClassOrDie(env, "android/graphics/HardwareRendererObserver");
+ gHardwareRendererObserverClassInfo.callback = GetMethodIDOrDie(env, observerClass,
+ "notifyDataAvailable", "()V");
+
+ return RegisterMethodsOrDie(env, "android/graphics/HardwareRendererObserver",
+ gMethods.data(), gMethods.size());
+
+}
+
+} // namespace android \ No newline at end of file
diff --git a/core/jni/android_graphics_HardwareRendererObserver.h b/core/jni/android_graphics_HardwareRendererObserver.h
new file mode 100644
index 000000000000..62111fd7d7a1
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "jni.h"
+
+#include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
+
+namespace android {
+
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
+class HardwareRendererObserver : public uirenderer::FrameMetricsObserver {
+public:
+ HardwareRendererObserver(JavaVM *vm, jobject observer);
+ ~HardwareRendererObserver();
+
+ /**
+ * Retrieves frame metrics for the oldest frame that the renderer has retained. The renderer
+ * will retain a buffer until it has been retrieved, via this method, or its internal storage
+ * is exhausted at which point it informs the caller of how many frames it has failed to store
+ * since the last time this method was invoked.
+ * @param env java env required to populate the provided buffer array
+ * @param metrics output parameter that represents the buffer of metrics that is to be filled
+ * @param dropCount output parameter that is updated to reflect the number of buffers that were
+ discarded since the last successful invocation of this method.
+ * @return true if there was data to populate the array and false otherwise. If false then
+ * neither the metrics buffer or dropCount will be modified.
+ */
+ bool getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount);
+
+ void notify(const int64_t* stats) override;
+
+private:
+ static constexpr int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
+ static constexpr int kRingSize = 3;
+
+ class FrameMetricsNotification {
+ public:
+ FrameMetricsNotification() {}
+
+ std::atomic_bool hasData = false;
+ int64_t buffer[kBufferSize];
+ int dropCount = 0;
+ private:
+ // non-copyable
+ FrameMetricsNotification(const FrameMetricsNotification&) = delete;
+ FrameMetricsNotification& operator=(const FrameMetricsNotification& ) = delete;
+ };
+
+ JavaVM* const mVm;
+ jweak mObserverWeak;
+
+ int mNextFree = 0;
+ int mNextInQueue = 0;
+ FrameMetricsNotification mRingBuffer[kRingSize];
+
+ int mDroppedReports = 0;
+};
+
+} // namespace android
diff --git a/core/jni/android_view_FrameMetricsObserver.cpp b/core/jni/android_view_FrameMetricsObserver.cpp
deleted file mode 100644
index febcb55bd0cd..000000000000
--- a/core/jni/android_view_FrameMetricsObserver.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "android_view_FrameMetricsObserver.h"
-
-namespace android {
-
-struct {
- jfieldID frameMetrics;
- jfieldID timingDataBuffer;
- jfieldID messageQueue;
- jmethodID callback;
-} gFrameMetricsObserverClassInfo;
-
-static JNIEnv* getenv(JavaVM* vm) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
- }
- return env;
-}
-
-static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
- jobject frameMetrics = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.frameMetrics);
- LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
- jobject buffer = env->GetObjectField(
- frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
- LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
- return reinterpret_cast<jlongArray>(buffer);
-}
-
-class NotifyHandler : public MessageHandler {
-public:
- NotifyHandler(JavaVM* vm, FrameMetricsObserverProxy* observer) : mVm(vm), mObserver(observer) {}
-
- virtual void handleMessage(const Message& message);
-
-private:
- JavaVM* const mVm;
- FrameMetricsObserverProxy* const mObserver;
-};
-
-void NotifyHandler::handleMessage(const Message& message) {
- JNIEnv* env = getenv(mVm);
-
- jobject target = env->NewLocalRef(mObserver->getObserverReference());
-
- if (target != nullptr) {
- jlongArray javaBuffer = get_metrics_buffer(env, target);
- int dropCount = 0;
- while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
- env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
- }
- env->DeleteLocalRef(target);
- }
-
- mObserver->decStrong(nullptr);
-}
-
-FrameMetricsObserverProxy::FrameMetricsObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
- JNIEnv* env = getenv(mVm);
-
- mObserverWeak = env->NewWeakGlobalRef(observer);
- LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
- "unable to create frame stats observer reference");
-
- jlongArray buffer = get_metrics_buffer(env, observer);
- jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
- LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
- "Mismatched Java/Native FrameMetrics data format.");
-
- jobject messageQueueLocal = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.messageQueue);
- mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
- LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
-
- mMessageHandler = new NotifyHandler(mVm, this);
- LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
- "OOM: unable to allocate NotifyHandler");
-}
-
-FrameMetricsObserverProxy::~FrameMetricsObserverProxy() {
- JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mObserverWeak);
-}
-
-bool FrameMetricsObserverProxy::getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
- FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
-
- if (elem.hasData.load()) {
- env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
- *dropCount = elem.dropCount;
- mNextInQueue = (mNextInQueue + 1) % kRingSize;
- elem.hasData = false;
- return true;
- }
-
- return false;
-}
-
-void FrameMetricsObserverProxy::notify(const int64_t* stats) {
- FrameMetricsNotification& elem = mRingBuffer[mNextFree];
-
- if (!elem.hasData.load()) {
- memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
-
- elem.dropCount = mDroppedReports;
- mDroppedReports = 0;
-
- incStrong(nullptr);
- mNextFree = (mNextFree + 1) % kRingSize;
- elem.hasData = true;
-
- mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
- } else {
- mDroppedReports++;
- }
-}
-
-int register_android_view_FrameMetricsObserver(JNIEnv* env) {
- jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
- gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
- env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
- gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
- env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
- gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
- env, observerClass, "notifyDataAvailable", "(I)V");
-
- jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
- gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
- env, metricsClass, "mTimingData", "[J");
- return JNI_OK;
-}
-
-} // namespace android \ No newline at end of file
diff --git a/core/jni/android_view_FrameMetricsObserver.h b/core/jni/android_view_FrameMetricsObserver.h
deleted file mode 100644
index 647f51c4492d..000000000000
--- a/core/jni/android_view_FrameMetricsObserver.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "jni.h"
-#include "core_jni_helpers.h"
-
-#include "android_os_MessageQueue.h"
-
-#include <FrameInfo.h>
-#include <FrameMetricsObserver.h>
-
-namespace android {
-
-/*
- * Implements JNI layer for hwui frame metrics reporting.
- */
-class FrameMetricsObserverProxy : public uirenderer::FrameMetricsObserver {
-public:
- FrameMetricsObserverProxy(JavaVM *vm, jobject observer);
-
- ~FrameMetricsObserverProxy();
-
- jweak getObserverReference() {
- return mObserverWeak;
- }
-
- bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount);
-
- virtual void notify(const int64_t* stats);
-
-private:
- static const int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
- static constexpr int kRingSize = 3;
-
- class FrameMetricsNotification {
- public:
- FrameMetricsNotification() : hasData(false) {}
-
- std::atomic_bool hasData;
- int64_t buffer[kBufferSize];
- int dropCount = 0;
- };
-
- JavaVM* const mVm;
- jweak mObserverWeak;
-
- sp<MessageQueue> mMessageQueue;
- sp<MessageHandler> mMessageHandler;
- Message mMessage;
-
- int mNextFree = 0;
- int mNextInQueue = 0;
- FrameMetricsNotification mRingBuffer[kRingSize];
-
- int mDroppedReports = 0;
-};
-
-} // namespace android
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 170e467a72cd..69ca17c08257 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -30,7 +30,7 @@
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
-#include "android_view_FrameMetricsObserver.h"
+#include "android_graphics_HardwareRendererObserver.h"
#include <private/EGL/cache.h>
@@ -580,28 +580,21 @@ static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
}
// ----------------------------------------------------------------------------
-// FrameMetricsObserver
+// HardwareRendererObserver
// ----------------------------------------------------------------------------
-static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
- jclass clazz, jlong proxyPtr, jobject fso) {
- JavaVM* vm = nullptr;
- if (env->GetJavaVM(&vm) != JNI_OK) {
- LOG_ALWAYS_FATAL("Unable to get Java VM");
- return 0;
- }
-
+static void android_view_ThreadedRenderer_addObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- FrameMetricsObserver* observer = new FrameMetricsObserverProxy(vm, fso);
renderProxy->addFrameMetricsObserver(observer);
- return reinterpret_cast<jlong>(observer);
}
-static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
+static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass clazz,
jlong proxyPtr, jlong observerPtr) {
- FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
@@ -675,12 +668,8 @@ static const JNINativeMethod gMethods[] = {
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback },
- { "nAddFrameMetricsObserver",
- "(JLandroid/view/FrameMetricsObserver;)J",
- (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
- { "nRemoveFrameMetricsObserver",
- "(JJ)V",
- (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
+ { "nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver },
+ { "nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver },
{ "nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
(void*)android_view_ThreadedRenderer_copySurfaceInto },
{ "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c4ac89acd0db..df5b02c22a2d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -31,6 +31,8 @@
// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
#include <sys/mount.h>
#include <linux/fs.h>
+#include <sys/types.h>
+#include <dirent.h>
#include <array>
#include <atomic>
@@ -40,6 +42,7 @@
#include <sstream>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <android/fdsan.h>
#include <arpa/inet.h>
@@ -51,6 +54,7 @@
#include <mntent.h>
#include <paths.h>
#include <signal.h>
+#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
@@ -159,6 +163,17 @@ static std::atomic_uint32_t gUsapPoolCount = 0;
*/
static int gUsapPoolEventFD = -1;
+static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
+
+/**
+ * Property to control if app data isolation is enabled.
+ */
+static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
+static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -663,7 +678,7 @@ static int UnmountTree(const char* path) {
return 0;
}
-static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+static void PrepareDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
fail_fn_t fail_fn) {
if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) {
fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s: %s",
@@ -671,6 +686,16 @@ static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
}
}
+static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+ fail_fn_t fail_fn) {
+ struct stat sb;
+ if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) != -1) {
+ // Directory exists already
+ return;
+ }
+ PrepareDir(dir, mode, uid, gid, fail_fn);
+}
+
static void BindMount(const std::string& source_dir, const std::string& target_dir,
fail_fn_t fail_fn) {
if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr,
@@ -680,6 +705,15 @@ static void BindMount(const std::string& source_dir, const std::string& target_d
}
}
+static void MountAppDataTmpFs(const std::string& target_dir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", target_dir.c_str(), "tmpfs",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC, "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs to %s: %s",
+ target_dir.c_str(), strerror(errno)));
+ }
+}
+
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
@@ -712,11 +746,19 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
bool isFuse = GetBoolProperty(kPropFuse, false);
- CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
if (isFuse) {
- BindMount(mount_mode == MOUNT_EXTERNAL_PASS_THROUGH ? pass_through_source : user_source,
- "/storage", fail_fn);
+ if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
+ MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
+ // For now, MediaProvider, installers and "full" get the pass_through mount
+ // view, which is currently identical to the sdcardfs write view.
+ //
+ // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+ BindMount(pass_through_source, "/storage", fail_fn);
+ } else {
+ BindMount(user_source, "/storage", fail_fn);
+ }
} else {
const std::string& storage_source = ExternalStorageViews[mount_mode];
BindMount(storage_source, "/storage", fail_fn);
@@ -1008,6 +1050,231 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
return pid;
}
+// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it
+// from the actual app data directory in data mirror.
+static void createAndMountAppData(std::string_view package_name,
+ std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path,
+ std::string_view actual_data_path, fail_fn_t fail_fn) {
+
+ char mirrorAppDataPath[PATH_MAX];
+ char actualAppDataPath[PATH_MAX];
+ snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(),
+ mirror_pkg_dir_name.data());
+ snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data());
+
+ PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn);
+
+ // Bind mount from original app data directory in mirror.
+ BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn);
+}
+
+// Get the directory name stored in /data/data. If device is unlocked it should be the same as
+// package name, otherwise it will be an encrypted name but with same inode number.
+static std::string getAppDataDirName(std::string_view parent_path, std::string_view package_name,
+ long long ce_data_inode, fail_fn_t fail_fn) {
+ // Check if directory exists
+ char tmpPath[PATH_MAX];
+ snprintf(tmpPath, PATH_MAX, "%s/%s", parent_path.data(), package_name.data());
+ struct stat s;
+ int err = stat(tmpPath, &s);
+ if (err == 0) {
+ // Directory exists, so return the directory name
+ return package_name.data();
+ } else {
+ if (errno != ENOENT) {
+ fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno)));
+ return nullptr;
+ }
+ // Directory doesn't exist, try to search the name from inode
+ DIR* dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == ce_data_inode) {
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+
+ // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted
+ // if ino_t is 32 bits.
+ ino_t fixed_ce_data_inode = 0;
+ if ((ce_data_inode & UPPER_HALF_WORD_MASK) == UPPER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ce_data_inode & LOWER_HALF_WORD_MASK;
+ } else if ((ce_data_inode & LOWER_HALF_WORD_MASK) == LOWER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK);
+ }
+ if (fixed_ce_data_inode != 0) {
+ dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == fixed_ce_data_inode) {
+ long long d_ino = ent->d_ino;
+ ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino);
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+ }
+ // Fallback done
+
+ fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
+ ce_data_inode, parent_path.data()));
+ return nullptr;
+ }
+}
+
+// Isolate app's data directory, by mounting a tmpfs on CE DE storage,
+// and create and bind mount app data in related_packages.
+static void isolateAppDataPerPackage(int userId, std::string_view package_name,
+ std::string_view volume_uuid, long long ce_data_inode, std::string_view actualCePath,
+ std::string_view actualDePath, fail_fn_t fail_fn) {
+
+ char mirrorCePath[PATH_MAX];
+ char mirrorDePath[PATH_MAX];
+ char mirrorCeParent[PATH_MAX];
+ snprintf(mirrorCeParent, PATH_MAX, "/data_mirror/data_ce/%s", volume_uuid.data());
+ snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId);
+ snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId);
+
+ createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn);
+
+ std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+ createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
+}
+
+/**
+ * Make other apps data directory not visible in CE, DE storage.
+ *
+ * Apps without app data isolation can detect if another app is installed on system,
+ * by "touching" other apps data directory like /data/data/com.whatsapp, if it returns
+ * "Permission denied" it means apps installed, otherwise it returns "File not found".
+ * Traditional file permissions or SELinux can only block accessing those directories but
+ * can't fix fingerprinting like this.
+ * We fix it by "overlaying" data directory, and only relevant app data packages exists
+ * in data directories.
+ *
+ * Steps:
+ * 1). Collect a list of all related apps (apps with same uid and whitelisted apps) data info
+ * (package name, data stored volume uuid, and inode number of its CE data directory)
+ * 2). Mount tmpfs on /data/data, /data/user(_de) and /mnt/expand, so apps no longer
+ * able to access apps data directly.
+ * 3). For each related app, create its app data directory and bind mount the actual content
+ * from apps data mirror directory. This works on both CE and DE storage, as DE storage
+ * is always available even storage is FBE locked, while we use inode number to find
+ * the encrypted DE directory in mirror so we can still bind mount it successfully.
+ *
+ * Example:
+ * 0). Assuming com.android.foo CE data is stored in /data/data and no shared uid
+ * 1). Mount a tmpfs on /data/data, /data/user, /data/user_de, /mnt/expand
+ * List = ["com.android.foo", "null" (volume uuid "null"=default),
+ * 123456 (inode number)]
+ * 2). On DE storage, we create a directory /data/user_de/0/com.com.android.foo, and bind
+ * mount (in the app's mount namespace) it from /data_mirror/data_de/0/com.android.foo.
+ * 3). We do similar for CE storage. But in direct boot mode, as /data_mirror/data_ce/0/ is
+ * encrypted, we can't find a directory with name com.android.foo on it, so we will
+ * use the inode number to find the right directory instead, which that directory content will
+ * be decrypted after storage is decrypted.
+ *
+ */
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ const userid_t userId = multiuser_get_user_id(uid);
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+ }
+
+ // Mount tmpfs on all possible data directories, so app no longer see the original apps data.
+ char internalCePath[PATH_MAX];
+ char internalLegacyCePath[PATH_MAX];
+ char internalDePath[PATH_MAX];
+ char externalPrivateMountPath[PATH_MAX];
+
+ snprintf(internalCePath, PATH_MAX, "/data/user");
+ snprintf(internalLegacyCePath, PATH_MAX, "/data/data");
+ snprintf(internalDePath, PATH_MAX, "/data/user_de");
+ snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+
+ MountAppDataTmpFs(internalLegacyCePath, fail_fn);
+ MountAppDataTmpFs(internalCePath, fail_fn);
+ MountAppDataTmpFs(internalDePath, fail_fn);
+ MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+
+ jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
+ std::string volUuid = extract_fn(vol_str).value();
+
+ jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
+ std::string inode = extract_fn(inode_str).value();
+ std::string::size_type sz;
+ long long ceDataInode = std::stoll(inode, &sz);
+
+ std::string actualCePath, actualDePath;
+ if (volUuid.compare("null") != 0) {
+ // Volume that is stored in /mnt/expand
+ char volPath[PATH_MAX];
+ char volCePath[PATH_MAX];
+ char volDePath[PATH_MAX];
+ char volCeUserPath[PATH_MAX];
+ char volDeUserPath[PATH_MAX];
+
+ snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
+ snprintf(volCePath, PATH_MAX, "%s/user", volPath);
+ snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
+ snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
+ snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
+
+ PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ actualCePath = volCeUserPath;
+ actualDePath = volDeUserPath;
+ } else {
+ // Internal volume that stored in /data
+ char internalCeUserPath[PATH_MAX];
+ char internalDeUserPath[PATH_MAX];
+ snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
+ snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
+ // If it's user 0, create a symlink /data/user/0 -> /data/data,
+ // otherwise create /data/user/$USER
+ if (userId == 0) {
+ symlink(internalLegacyCePath, internalCeUserPath);
+ actualCePath = internalLegacyCePath;
+ } else {
+ PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualCePath = internalCeUserPath;
+ }
+ PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualDePath = internalDeUserPath;
+ }
+ isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
+ actualCePath, actualDePath, fail_fn);
+ }
+}
+
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1015,7 +1282,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint mount_external, jstring managed_se_info,
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
- jstring managed_app_data_dir, bool is_top_app) {
+ jstring managed_app_data_dir, bool is_top_app,
+ jobjectArray pkg_data_info_list) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1051,6 +1319,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
+ // System services, isolated process, webview/app zygote, old target sdk app, should
+ // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
+ // Isolated process / webview / app zygote should be gated by SELinux and file permission
+ // so they can't even traverse CE / DE directories.
+ if (pkg_data_info_list != nullptr
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
+ fail_fn);
+ }
+
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
@@ -1417,7 +1695,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
- jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -1449,7 +1728,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
return pid;
}
@@ -1473,10 +1752,13 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
fds_to_ignore,
true);
if (pid == 0) {
+ // System server prcoess does not need data isolation so no need to
+ // know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
- false, nullptr, nullptr, /* is_top_app= */ false);
+ false, nullptr, nullptr, /* is_top_app= */ false,
+ /* pkg_data_info_list */ nullptr);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -1599,14 +1881,15 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
/**
@@ -1767,7 +2050,7 @@ static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env,
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
(void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
(void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -1780,7 +2063,7 @@ static const JNINativeMethod gMethods[] = {
{ "nativeForkUsap", "(II[IZ)I",
(void *) com_android_internal_os_Zygote_nativeForkUsap },
{ "nativeSpecializeAppProcess",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
{ "nativeInitNativeState", "(Z)V",
(void *) com_android_internal_os_Zygote_nativeInitNativeState },
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index e3e07de3556e..44581af10b95 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2477,4 +2477,31 @@ enum PageId {
// CATEGORY: SETTINGS
// OS: R
PANEL_ADD_WIFI_NETWORKS = 1809;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
+ // gesture mode
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+
+ // OPEN: Settings > Accessibility > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT = 1812;
+
+ // OPEN: Settings > Accessibility > Magnification > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_MAGNIFICATION_EDIT_SHORTCUT = 1813;
+
+ // OPEN: Settings > Accessibility > Color correction > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_DALTONIZER_EDIT_SHORTCUT = 1814;
+
}
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index f648912d36eb..5963f6a7f938 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -56,6 +56,7 @@ enum Root {
ROOT_VIDEOS = 9;
ROOT_MTP = 10;
ROOT_THIRD_PARTY_APP = 11;
+ ROOT_DOCUMENTS = 12;
}
enum ContextScope {
diff --git a/core/proto/android/util/quotatracker.proto b/core/proto/android/util/quotatracker.proto
new file mode 100644
index 000000000000..0dea853b0986
--- /dev/null
+++ b/core/proto/android/util/quotatracker.proto
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+
+package android.util.quota;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// A com.android.util.quota.QuotaTracker object.
+message QuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool is_enabled = 1;
+
+ // If quota is free for everything in the tracker.
+ optional bool is_global_quota_free = 2;
+
+ // Current elapsed realtime.
+ optional int64 elapsed_realtime = 3;
+
+ message AlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the listener is waiting for an alarm or not.
+ optional bool is_waiting = 1;
+ // The time at which the alarm should go off, in the elapsed realtime timebase. Only
+ // valid if is_waiting is true.
+ optional int64 trigger_time_elapsed = 2;
+ }
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.Category object.
+message CategoryProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Name of the category set by the system service.
+ optional string name = 1;
+}
+
+// A com.android.util.quota.Uptc object.
+message UptcProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // UserHandle value. Should be 0, 10, 11, 12, etc. where 0 is the owner.
+ optional int32 user_id = 1;
+ // Package name
+ optional string name = 2;
+ // Tag set by the system service to differentiate calls.
+ optional string tag = 3;
+}
+
+// A com.android.util.quota.CountQuotaTracker object.
+message CountQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message CountLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int32 limit = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated CountLimit count_limit = 2;
+
+ message Event {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time the event occurred, in the elapsed realtime timebase.
+ optional int64 timestamp_elapsed = 1;
+ }
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int64 window_size_ms = 2;
+ optional int32 count_limit = 3;
+
+ // The total number of events that occurred in the window.
+ optional int32 count_in_window = 4;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // count_in_window >= count_limit.
+ optional int64 in_quota_time_elapsed = 5;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ repeated Event events = 3;
+
+ repeated ExecutionStats execution_stats = 4;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 5;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.DurationQuotaTracker object.
+message DurationQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message DurationLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int64 limit_ms = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated DurationLimit duration_limit = 2;
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int32 window_size_ms = 2;
+ optional int64 duration_limit_ms = 3;
+
+ // The overall session duration in the window.
+ optional int64 session_duration_in_window_ms = 4;
+ // The number of individual long-running events in the window.
+ optional int32 event_count_in_window = 5;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // session_duration_in_window_ms >= duration_limit_ms.
+ optional int64 in_quota_time_elapsed = 6;
+ }
+
+ message Timer {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // True if the Timer is actively tracking long-running events.
+ optional bool is_active = 1;
+ // The time this timer last became active. Only valid if is_active is true.
+ optional int64 start_time_elapsed = 2;
+ // How many long-running events are currently running. Valid only if is_active is true.
+ optional int32 event_count = 3;
+ }
+
+ message TimingSession {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 start_time_elapsed = 1;
+ optional int64 end_time_elapsed = 2;
+ // How many events started during this session. This only count long-running events, not
+ // instantaneous events.
+ optional int32 event_count = 3;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ optional Timer timer = 3;
+
+ repeated TimingSession saved_sessions = 4;
+
+ repeated ExecutionStats execution_stats = 5;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 6;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ message ReachedQuotaAlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 trigger_time_elapsed = 1;
+
+ message UptcTimes {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+ optional int64 out_of_quota_time_elapsed = 2;
+ }
+ repeated UptcTimes uptc_times = 2;
+ }
+ optional ReachedQuotaAlarmListener reached_quota_alarm_listener = 4;
+
+ // Next tag: 5
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cb5b4a595ac3..ba2f64d6db5f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1850,6 +1850,13 @@
android:description="@string/permdesc_vibrate"
android:protectionLevel="normal|instant" />
+ <!-- Allows access to the vibrator always-on settings.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
+ android:protectionLevel="signature" />
+
<!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
from dimming.
<p>Protection level: normal
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b93eec6..4d7846dfb9cc 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -45,7 +45,8 @@
android:ellipsize="end"
android:fontFamily="@android:string/config_headlineFontFamily"
android:textColor="?android:attr/textColorPrimary"
- android:maxLines="2"/>
+ android:maxLines="2"
+ android:focusable="true"/>
<LinearLayout
android:id="@+id/copy_button"
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 3f3ad578e8d7..3b864139cf71 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,7 +28,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.TimeUtils;
-import android.view.FrameMetricsObserver;
import android.view.IGraphicsStats;
import android.view.IGraphicsStatsCallback;
import android.view.NativeVectorDrawableAnimator;
@@ -38,8 +37,6 @@ import android.view.SurfaceHolder;
import android.view.TextureLayer;
import android.view.animation.AnimationUtils;
-import com.android.internal.util.VirtualRefBasePtr;
-
import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -598,9 +595,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void addFrameMetricsObserver(FrameMetricsObserver observer) {
- long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
- observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ public void addObserver(HardwareRendererObserver observer) {
+ nAddObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -608,9 +604,8 @@ public class HardwareRenderer {
*
* @hide
*/
- public void removeFrameMetricsObserver(FrameMetricsObserver observer) {
- nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
- observer.mNative = null;
+ public void removeObserver(HardwareRendererObserver observer) {
+ nRemoveObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -1170,10 +1165,9 @@ public class HardwareRenderer {
private static native void nSetFrameCompleteCallback(long nativeProxy,
FrameCompleteCallback callback);
- private static native long nAddFrameMetricsObserver(long nativeProxy,
- FrameMetricsObserver observer);
+ private static native void nAddObserver(long nativeProxy, long nativeObserver);
- private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+ private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
private static native int nCopySurfaceInto(Surface surface,
int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
new file mode 100644
index 000000000000..da9d03c639f7
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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 android.graphics;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * Provides streaming access to frame stats information from HardwareRenderer to apps.
+ *
+ * @hide
+ */
+public class HardwareRendererObserver {
+ private final long[] mFrameMetrics;
+ private final Handler mHandler;
+ private final OnFrameMetricsAvailableListener mListener;
+ private VirtualRefBasePtr mNativePtr;
+
+ /**
+ * Interface for clients that want frame timing information for each frame rendered.
+ * @hide
+ */
+ public interface OnFrameMetricsAvailableListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too long to execute, as the report producer
+ * cannot wait for the consumer to complete.
+ *
+ * It is highly recommended that clients copy the metrics array within this method
+ * and defer additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onFrameMetricsAvailable(int dropCountSinceLastInvocation);
+ }
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param frameMetrics the available metrics. This array is reused on every call to the listener
+ * and thus <strong>this reference should only be used within the scope of the listener callback
+ * as data is not guaranteed to be valid outside the scope of that method</strong>.
+ * @param handler the Handler to use when invoking callbacks
+ */
+ public HardwareRendererObserver(@NonNull OnFrameMetricsAvailableListener listener,
+ @NonNull long[] frameMetrics, @NonNull Handler handler) {
+ if (handler == null || handler.getLooper() == null) {
+ throw new NullPointerException("handler and its looper cannot be null");
+ }
+
+ if (handler.getLooper().getQueue() == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = frameMetrics;
+ mHandler = handler;
+ mListener = listener;
+ mNativePtr = new VirtualRefBasePtr(nCreateObserver());
+ }
+
+ /*package*/ long getNativeInstance() {
+ return mNativePtr.get();
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mHandler.post(() -> {
+ boolean hasMoreData = true;
+ while (hasMoreData) {
+ // a drop count of -1 is a sentinel that no more buffers are available
+ int dropCount = nGetNextBuffer(mNativePtr.get(), mFrameMetrics);
+ if (dropCount >= 0) {
+ mListener.onFrameMetricsAvailable(dropCount);
+ } else {
+ hasMoreData = false;
+ }
+ }
+ });
+ }
+
+ private native long nCreateObserver();
+ private static native int nGetNextBuffer(long nativePtr, long[] data);
+}
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 000000000000..88a829546989
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2019 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+ public static final String TAG = "MediaMetrics";
+
+ /**
+ * The TYPE constants below should match those in native MediaMetricsItem.h
+ */
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_INT32 = 1; // Java integer
+ private static final int TYPE_INT64 = 2; // Java long
+ private static final int TYPE_DOUBLE = 3; // Java double
+ private static final int TYPE_CSTRING = 4; // Java string
+ private static final int TYPE_RATE = 5; // Two longs, ignored in Java
+
+ // The charset used for encoding Strings to bytes.
+ private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+ /**
+ * Item records properties and delivers to the MediaMetrics service
+ *
+ */
+ public static class Item {
+
+ /*
+ * MediaMetrics Item
+ *
+ * Creates a Byte String and sends to the MediaMetrics service.
+ * The Byte String serves as a compact form for logging data
+ * with low overhead for storage.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * To record a MediaMetrics event, one creates a new item with an id,
+ * then use a series of puts to add properties
+ * and then a record() to send to the MediaMetrics service.
+ *
+ * The properties may not be unique, and putting a later property with
+ * the same name as an earlier property will overwrite the value and type
+ * of the prior property.
+ *
+ * The timestamp can only be recorded by a system service (and is ignored otherwise;
+ * the MediaMetrics service will fill in the timestamp as needed).
+ *
+ * The units of time are in SystemClock.elapsedRealtimeNanos().
+ *
+ * A clear() may be called to reset the properties to empty, the time to 0, but keep
+ * the other entries the same. This may be called after record().
+ * Additional properties may be added after calling record(). Changing the same property
+ * repeatedly is discouraged as - for this particular implementation - extra data
+ * is stored per change.
+ *
+ * new MediaMetrics.Item(mSomeId)
+ * .putString("event", "javaCreate")
+ * .putInt("value", intValue)
+ * .record();
+ */
+
+ /**
+ * Creates an Item with server added uid, time.
+ *
+ * This is the typical way to record a MediaMetrics item.
+ *
+ * @param key the Metrics ID associated with the item.
+ */
+ public Item(String key) {
+ this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+ 2048 /* capacity */);
+ }
+
+ /**
+ * Creates an Item specifying pid, uid, time, and initial Item capacity.
+ *
+ * This might be used by a service to specify a different PID or UID for a client.
+ *
+ * @param key the Metrics ID associated with the item.
+ * An app may only set properties on an item which has already been
+ * logged previously by a service.
+ * @param pid the process ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param uid the user ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param timeNs the time when the item occurred (may be in the past).
+ * A value of 0 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill it in.
+ * Should be obtained from SystemClock.elapsedRealtimeNanos().
+ * @param capacity the anticipated size to use for the buffer.
+ * If the capacity is too small, the buffer will be resized to accommodate.
+ * This is amortized to copy data no more than twice.
+ */
+ public Item(String key, int pid, int uid, long timeNs, int capacity) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE - 1) {
+ throw new IllegalArgumentException("Key length too large");
+ }
+
+ // Version 0 - compute the header offsets here.
+ mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+ mPidOffset = mHeaderSize - 16;
+ mUidOffset = mHeaderSize - 12;
+ mTimeNsOffset = mHeaderSize - 8;
+ mPropertyCountOffset = mHeaderSize;
+ mPropertyStartOffset = mHeaderSize + 4;
+
+ mKey = key;
+ mBuffer = ByteBuffer.allocateDirect(
+ Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+ // Version 0 - fill the ByteBuffer with the header (some details updated later).
+ mBuffer.order(ByteOrder.nativeOrder())
+ .putInt((int) 0) // total size in bytes (filled in later)
+ .putInt((int) mHeaderSize) // size of header
+ .putChar((char) FORMAT_VERSION) // version
+ .putChar((char) (keyLength + 1)) // length, with zero termination
+ .put(keyBytes).put((byte) 0)
+ .putInt(pid)
+ .putInt(uid)
+ .putLong(timeNs);
+ if (mHeaderSize != mBuffer.position()) {
+ throw new IllegalStateException("Mismatched sizing");
+ }
+ mBuffer.putInt(0); // number of properties (to be later filled in by record()).
+ }
+
+ /**
+ * Sets the property with key to an integer (32 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putInt(String key, int value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT32)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putInt(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a long (64 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putLong(String key, long value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT64)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putLong(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a double value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putDouble(String key, double value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_DOUBLE)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putDouble(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a String value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putString(String key, String value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_CSTRING)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .put(valueBytes).put((byte) 0); // value, zero term.
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the pid to the provided value.
+ *
+ * @param pid which can be -1 if the service is to fill it in from the calling info.
+ * @return itself
+ */
+ public Item setPid(int pid) {
+ mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the uid to the provided value.
+ *
+ * The UID represents the client associated with the property. This must be the UID
+ * of the application if it comes from the application client.
+ *
+ * Trusted services are allowed to set the uid for a client-related item.
+ *
+ * @param uid which can be -1 if the service is to fill it in from calling info.
+ * @return itself
+ */
+ public Item setUid(int uid) {
+ mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the timestamp to the provided value.
+ *
+ * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+ * This should be associated with the occurrence of the event. It is recommended that
+ * the event be registered immediately when it occurs, and no later than 500ms
+ * (and certainly not in the future).
+ *
+ * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+ * @return itself
+ */
+ public Item setTimestamp(long timeNs) {
+ mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+ return this;
+ }
+
+ /**
+ * Clears the properties and resets the time to 0.
+ *
+ * No other values are changed.
+ *
+ * @return itself
+ */
+ public Item clear() {
+ mBuffer.position(mPropertyStartOffset);
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+ mPropertyCount = 0;
+ return this;
+ }
+
+ /**
+ * Sends the item to the MediaMetrics service.
+ *
+ * The item properties are unchanged, hence record() may be called more than once
+ * to send the same item twice. Also, record() may be called without any properties.
+ *
+ * @return true if successful.
+ */
+ public boolean record() {
+ updateHeader();
+ return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+ }
+
+ /**
+ * Converts the Item to a Bundle.
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @return a Bundle with the keys set according to data in the Item's buffer.
+ */
+ @TestApi
+ public Bundle toBundle() {
+ updateHeader();
+
+ final ByteBuffer buffer = mBuffer.duplicate();
+ buffer.order(ByteOrder.nativeOrder()) // restore order property
+ .flip(); // convert from write buffer to read buffer
+
+ return toBundle(buffer);
+ }
+
+ // The following constants are used for tests to extract
+ // the content of the Bundle for CTS testing.
+ @TestApi
+ public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+ @TestApi
+ public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+ @TestApi
+ public static final String BUNDLE_VERSION = "_version";
+ @TestApi
+ public static final String BUNDLE_KEY_SIZE = "_keySize";
+ @TestApi
+ public static final String BUNDLE_KEY = "_key";
+ @TestApi
+ public static final String BUNDLE_PID = "_pid";
+ @TestApi
+ public static final String BUNDLE_UID = "_uid";
+ @TestApi
+ public static final String BUNDLE_TIMESTAMP = "_timestamp";
+ @TestApi
+ public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+ /**
+ * Converts a buffer contents to a bundle
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @param buffer contains the byte data serialized according to the byte string version.
+ * @return a Bundle with the keys set according to data in the buffer.
+ */
+ @TestApi
+ public static Bundle toBundle(ByteBuffer buffer) {
+ final Bundle bundle = new Bundle();
+
+ final int totalSize = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final char version = buffer.getChar();
+ final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+ if (totalSize < 0 || headerSize < 0) {
+ throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+ }
+ final String key;
+ if (keySize > 0) {
+ key = getStringFromBuffer(buffer, keySize);
+ } else {
+ throw new IllegalArgumentException("Illegal null key");
+ }
+
+ final int pid = buffer.getInt();
+ final int uid = buffer.getInt();
+ final long timestamp = buffer.getLong();
+
+ // Verify header size (depending on version).
+ final int headerRead = buffer.position();
+ if (version == 0) {
+ if (headerRead != headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+ }
+ } else {
+ // future versions should only increase header size
+ // by adding to the end.
+ if (headerRead > headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+ } else if (headerRead < headerSize) {
+ buffer.position(headerSize);
+ }
+ }
+
+ // Body always starts with properties.
+ final int propertyCount = buffer.getInt();
+ if (propertyCount < 0) {
+ throw new IllegalArgumentException(
+ "Cannot have more than " + Integer.MAX_VALUE + " properties");
+ }
+ bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+ bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+ bundle.putChar(BUNDLE_VERSION, version);
+ bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+ bundle.putString(BUNDLE_KEY, key);
+ bundle.putInt(BUNDLE_PID, pid);
+ bundle.putInt(BUNDLE_UID, uid);
+ bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+ bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+ for (int i = 0; i < propertyCount; ++i) {
+ final int initialBufferPosition = buffer.position();
+ final char propSize = buffer.getChar();
+ final byte type = buffer.get();
+
+ // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+ final String propKey = getStringFromBuffer(buffer);
+ switch (type) {
+ case TYPE_INT32:
+ bundle.putInt(propKey, buffer.getInt());
+ break;
+ case TYPE_INT64:
+ bundle.putLong(propKey, buffer.getLong());
+ break;
+ case TYPE_DOUBLE:
+ bundle.putDouble(propKey, buffer.getDouble());
+ break;
+ case TYPE_CSTRING:
+ bundle.putString(propKey, getStringFromBuffer(buffer));
+ break;
+ case TYPE_NONE:
+ break; // ignore on Java side
+ case TYPE_RATE:
+ buffer.getLong(); // consume the first int64_t of rate
+ buffer.getLong(); // consume the second int64_t of rate
+ break; // ignore on Java side
+ default:
+ // These are unsupported types for version 0
+ // We ignore them if the version is greater than 0.
+ if (version == 0) {
+ throw new IllegalArgumentException(
+ "Property " + propKey + " has unsupported type " + type);
+ }
+ buffer.position(initialBufferPosition + propSize); // advance and skip
+ break;
+ }
+ final int deltaPosition = buffer.position() - initialBufferPosition;
+ if (deltaPosition != propSize) {
+ throw new IllegalArgumentException("propSize:" + propSize
+ + " != deltaPosition:" + deltaPosition);
+ }
+ }
+
+ final int finalPosition = buffer.position();
+ if (finalPosition != totalSize) {
+ throw new IllegalArgumentException("totalSize:" + totalSize
+ + " != finalPosition:" + finalPosition);
+ }
+ return bundle;
+ }
+
+ // Version 0 byte offsets for the header.
+ private static final int FORMAT_VERSION = 0;
+ private static final int TOTAL_SIZE_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 4;
+ private static final int MINIMUM_PAYLOAD_SIZE = 4;
+ private final int mPidOffset; // computed in constructor
+ private final int mUidOffset; // computed in constructor
+ private final int mTimeNsOffset; // computed in constructor
+ private final int mPropertyCountOffset; // computed in constructor
+ private final int mPropertyStartOffset; // computed in constructor
+ private final int mHeaderSize; // computed in constructor
+
+ private final String mKey;
+
+ private ByteBuffer mBuffer; // may be reallocated if capacity is insufficient.
+ private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+ private int reserveProperty(byte[] keyBytes, int payloadSize) {
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE) {
+ throw new IllegalStateException("property key too long "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET));
+ }
+ if (payloadSize > Character.MAX_VALUE) {
+ throw new IllegalStateException("payload too large " + payloadSize);
+ }
+
+ // See the byte string property format above.
+ final int size = 2 /* length */
+ + 1 /* type */
+ + keyLength + 1 /* key length with zero termination */
+ + payloadSize; /* payload size */
+
+ if (size > Character.MAX_VALUE) {
+ throw new IllegalStateException("Item property "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+ }
+
+ if (mBuffer.remaining() < size) {
+ int newCapacity = mBuffer.position() + size;
+ if (newCapacity > Integer.MAX_VALUE >> 1) {
+ throw new IllegalStateException(
+ "Item memory requirements too large: " + newCapacity);
+ }
+ newCapacity <<= 1;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+ buffer.order(ByteOrder.nativeOrder());
+
+ // Copy data from old buffer to new buffer.
+ mBuffer.flip();
+ buffer.put(mBuffer);
+
+ // set buffer to new buffer
+ mBuffer = buffer;
+ }
+ return size;
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer) {
+ return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+ int i = buffer.position();
+ int limit = buffer.limit();
+ if (size < Integer.MAX_VALUE - i && i + size < limit) {
+ limit = i + size;
+ }
+ for (; i < limit; ++i) {
+ if (buffer.get(i) == 0) {
+ final int newPosition = i + 1;
+ if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+ throw new IllegalArgumentException("chars consumed at " + i + ": "
+ + (newPosition - buffer.position()) + " != size: " + size);
+ }
+ final String found;
+ if (buffer.hasArray()) {
+ found = new String(
+ buffer.array(), buffer.position() + buffer.arrayOffset(),
+ i - buffer.position(), MEDIAMETRICS_CHARSET);
+ buffer.position(newPosition);
+ } else {
+ final byte[] array = new byte[i - buffer.position()];
+ buffer.get(array);
+ found = new String(array, MEDIAMETRICS_CHARSET);
+ buffer.get(); // remove 0.
+ }
+ return found;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No zero termination found in string position: "
+ + buffer.position() + " end: " + i);
+ }
+
+ /**
+ * May be called multiple times - just makes the header consistent with the current
+ * properties written.
+ */
+ private void updateHeader() {
+ // Buffer sized properly in constructor.
+ mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position()) // set total length
+ .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+ }
+ }
+
+ private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3e6f4c01873c..bad0ef4104fe 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -51,7 +52,6 @@ import java.util.concurrent.Executor;
* @hide
*/
public class MediaRouter2 {
-
/** @hide */
@Retention(SOURCE)
@IntDef(value = {
@@ -102,13 +102,11 @@ public class MediaRouter2 {
new CopyOnWriteArrayList<>();
private final String mPackageName;
+ @GuardedBy("sLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- //TODO: Use a lock for this to cover the below use case
- // mRouter.setControlCategories(...);
- // routes = mRouter.getRoutes();
- // The current implementation returns empty list
- private volatile List<String> mControlCategories = Collections.emptyList();
+ @GuardedBy("sLock")
+ private List<String> mControlCategories = Collections.emptyList();
private MediaRoute2Info mSelectedRoute;
@GuardedBy("sLock")
@@ -117,7 +115,9 @@ public class MediaRouter2 {
private Client2 mClient;
final Handler mHandler;
- volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
+ @GuardedBy("sLock")
+ private boolean mShouldUpdateRoutes;
+ private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
/**
* Gets an instance of the media router associated with the context.
@@ -171,8 +171,7 @@ public class MediaRouter2 {
/**
* Registers a callback to discover routes and to receive events when they change.
* <p>
- * If you register the same callback twice or more, the previous arguments will be overwritten
- * with the new arguments.
+ * If you register the same callback twice or more, it will be ignored.
* </p>
*/
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@@ -180,18 +179,10 @@ public class MediaRouter2 {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord record;
- // This is required to prevent adding the same callback twice.
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- record = new CallbackRecord(callback);
- mCallbackRecords.add(record);
- } else {
- record = mCallbackRecords.get(index);
- }
- record.mExecutor = executor;
- record.mFlags = flags;
+ CallbackRecord record = new CallbackRecord(callback, executor, flags);
+ if (!mCallbackRecords.addIfAbsent(record)) {
+ Log.w(TAG, "Ignoring the same callback");
+ return;
}
synchronized (sLock) {
@@ -206,8 +197,6 @@ public class MediaRouter2 {
}
}
}
- //TODO: Is it thread-safe?
- record.notifyRoutes();
//TODO: Update discovery request here.
}
@@ -222,23 +211,20 @@ public class MediaRouter2 {
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignoring to remove unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterClient2(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router.", ex);
- }
- //TODO: Clean up mRoutes. (onHandler?)
- mClient = null;
+ if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
+ Log.w(TAG, "Ignoring unknown callback");
+ return;
+ }
+
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterClient2(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router.", ex);
}
+ //TODO: Clean up mRoutes. (onHandler?)
+ mClient = null;
}
}
}
@@ -246,26 +232,52 @@ public class MediaRouter2 {
//TODO(b/139033746): Rename "Control Category" when it's finalized.
/**
* Sets the control categories of the application.
- * Routes that support at least one of the given control categories only exists and are handled
+ * Routes that support at least one of the given control categories are handled
* by the media router.
*/
public void setControlCategories(@NonNull Collection<String> controlCategories) {
Objects.requireNonNull(controlCategories, "control categories must not be null");
- // To ensure invoking callbacks correctly according to control categories
- mHandler.sendMessage(obtainMessage(MediaRouter2::setControlCategoriesOnHandler,
- MediaRouter2.this, new ArrayList<>(controlCategories)));
+ List<String> newControlCategories = new ArrayList<>(controlCategories);
+
+ synchronized (sLock) {
+ mShouldUpdateRoutes = true;
+
+ // invoke callbacks due to control categories change
+ handleControlCategoriesChangedLocked(newControlCategories);
+ if (mClient != null) {
+ try {
+ mMediaRouterService.setControlCategories(mClient, mControlCategories);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to set control categories.", ex);
+ }
+ }
+ }
}
/**
* Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
* known to the media router.
+ * Please note that the list can be changed before callbacks are invoked.
*
* @return the list of routes that support at least one of the control categories set by
* the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
+ synchronized (sLock) {
+ if (mShouldUpdateRoutes) {
+ mShouldUpdateRoutes = false;
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : mRoutes.values()) {
+ if (route.supportsControlCategory(mControlCategories)) {
+ filteredRoutes.add(route);
+ }
+ }
+ mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
+ }
+ }
return mFilteredRoutes;
}
@@ -379,43 +391,16 @@ public class MediaRouter2 {
}
}
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- CallbackRecord callbackRecord = mCallbackRecords.get(i);
- if (callbackRecord.mCallback == callback) {
- return i;
- }
- }
- return -1;
- }
-
- private void setControlCategoriesOnHandler(List<String> newControlCategories) {
- List<String> prevControlCategories = mControlCategories;
+ private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ List<String> prevControlCategories = mControlCategories;
mControlCategories = newControlCategories;
- Client2 client;
- synchronized (sLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- mMediaRouterService.setControlCategories(client, mControlCategories);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set control categories.", ex);
- }
- }
for (MediaRoute2Info route : mRoutes.values()) {
boolean preSupported = route.supportsControlCategory(prevControlCategories);
boolean postSupported = route.supportsControlCategory(newControlCategories);
- if (postSupported) {
- filteredRoutes.add(route);
- }
if (preSupported == postSupported) {
continue;
}
@@ -425,13 +410,14 @@ public class MediaRouter2 {
addedRoutes.add(route);
}
}
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
if (removedRoutes.size() > 0) {
- notifyRoutesRemoved(removedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
+ MediaRouter2.this, removedRoutes));
}
if (addedRoutes.size() > 0) {
- notifyRoutesAdded(addedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
+ MediaRouter2.this, addedRoutes));
}
}
@@ -441,42 +427,47 @@ public class MediaRouter2 {
// 2) Call onRouteSelected(system_route, reason_fallback) if previously selected route
// does not exist anymore. => We may need 'boolean MediaRoute2Info#isSystemRoute()'.
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- addedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategory(mControlCategories)) {
+ addedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (addedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesAdded(addedRoutes);
}
}
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
- if (route.supportsControlCategory(mControlCategories)) {
- removedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.remove(route.getUniqueId());
+ if (route.supportsControlCategory(mControlCategories)) {
+ removedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (removedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesRemoved(removedRoutes);
}
}
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- changedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategory(mControlCategories)) {
+ changedRoutes.add(route);
+ }
}
}
if (changedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesChanged(changedRoutes);
}
}
@@ -500,17 +491,6 @@ public class MediaRouter2 {
notifyRouteSelected(route, reason, controlHints);
}
- private void refreshFilteredRoutes() {
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategory(mControlCategories)) {
- filteredRoutes.add(route);
- }
- }
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
- }
-
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -544,13 +524,16 @@ public class MediaRouter2 {
*/
public static class Callback {
/**
- * Called when routes are added.
+ * Called when routes are added. Whenever you registers a callback, this will
+ * be invoked with known routes.
+ *
* @param routes the list of routes that have been added. It's never empty.
*/
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
+ *
* @param routes the list of routes that have been removed. It's never empty.
*/
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
@@ -569,6 +552,7 @@ public class MediaRouter2 {
/**
* Called when a route is selected. Exactly one route can be selected at a time.
+ *
* @param route the selected route.
* @param reason the reason why the route is selected.
* @param controlHints An optional bundle of provider-specific arguments which may be
@@ -587,16 +571,26 @@ public class MediaRouter2 {
public Executor mExecutor;
public int mFlags;
- CallbackRecord(@NonNull Callback callback) {
+ CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
mCallback = callback;
+ mExecutor = executor;
+ mFlags = flags;
}
- void notifyRoutes() {
- final List<MediaRoute2Info> routes = mFilteredRoutes;
- // notify only when bound to media router service.
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
+ }
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index d56dd11fb8f2..502538d375ac 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -57,7 +57,7 @@ public class MediaRouter2Manager {
private Client mClient;
private final IMediaRouterService mMediaRouterService;
final Handler mHandler;
- final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
+ final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
private final Object mRoutesLock = new Object();
@GuardedBy("mRoutesLock")
@@ -99,14 +99,10 @@ public class MediaRouter2Manager {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord callbackRecord;
- synchronized (mCallbackRecords) {
- if (findCallbackRecordIndexLocked(callback) >= 0) {
- Log.w(TAG, "Ignoring to add the same callback twice.");
- return;
- }
- callbackRecord = new CallbackRecord(executor, callback);
- mCallbackRecords.add(callbackRecord);
+ CallbackRecord callbackRecord = new CallbackRecord(executor, callback);
+ if (!mCallbackRecords.addIfAbsent(callbackRecord)) {
+ Log.w(TAG, "Ignoring to add the same callback twice.");
+ return;
}
synchronized (sLock) {
@@ -118,8 +114,6 @@ public class MediaRouter2Manager {
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router manager.", ex);
}
- } else {
- callbackRecord.notifyRoutes();
}
}
}
@@ -132,36 +126,23 @@ public class MediaRouter2Manager {
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignore removing unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
- }
- //TODO: clear mRoutes?
- mClient = null;
- }
- }
+ if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
+ Log.w(TAG, "Ignore removing unknown callback. " + callback);
+ return;
}
- }
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- if (mCallbackRecords.get(i).mCallback == callback) {
- return i;
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterManager(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router manager", ex);
+ }
+ //TODO: clear mRoutes?
+ mClient = null;
+ mControlCategoryMap.clear();
}
}
- return -1;
}
//TODO: Use cache not to create array. For now, it's unclear when to purge the cache.
@@ -187,7 +168,6 @@ public class MediaRouter2Manager {
}
}
}
- //TODO: Should we cache this?
return routes;
}
@@ -342,10 +322,14 @@ public class MediaRouter2Manager {
}
void updateControlCategories(String packageName, List<String> categories) {
- mControlCategoryMap.put(packageName, categories);
+ List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
+ if ((prevCategories == null && categories.size() == 0)
+ || Objects.equals(categories, prevCategories)) {
+ return;
+ }
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName));
+ () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
}
}
@@ -386,8 +370,10 @@ public class MediaRouter2Manager {
* Called when the control categories of an app is changed.
*
* @param packageName the package name of the application
+ * @param controlCategories the list of control categories set by an application.
*/
- public void onControlCategoriesChanged(@NonNull String packageName) {}
+ public void onControlCategoriesChanged(@NonNull String packageName,
+ @NonNull List<String> controlCategories) {}
}
final class CallbackRecord {
@@ -399,14 +385,20 @@ public class MediaRouter2Manager {
mCallback = callback;
}
- void notifyRoutes() {
- List<MediaRoute2Info> routes;
- synchronized (mRoutesLock) {
- routes = new ArrayList<>(mRoutes.values());
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
}
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
new file mode 100644
index 000000000000..5ff721837573
--- /dev/null
+++ b/media/java/android/media/RouteSessionController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2019 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 android.media;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * A class to control media route session in media route provider.
+ * For example, adding/removing/transferring routes to session can be done through this class.
+ * Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
+ *
+ * @hide
+ */
+public class RouteSessionController {
+ private final int mSessionId;
+ private final String mCategory;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+ new CopyOnWriteArrayList<>();
+
+ private volatile boolean mIsReleased;
+
+ /**
+ * @param sessionId the ID of the session.
+ * @param category The category of media routes that the session includes.
+ */
+ RouteSessionController(int sessionId, @NonNull String category) {
+ mSessionId = sessionId;
+ mCategory = category;
+ }
+
+ /**
+ * @return the ID of this controller
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * @return the category of routes that the session includes.
+ */
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * @return the list of currently connected routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getRoutes() {
+ // TODO: Implement this when SessionInfo is introduced.
+ return null;
+ }
+
+ /**
+ * Returns true if the session is released, false otherwise.
+ * If it is released, then all other getters from this instance may return invalid values.
+ * Also, any operations to this instance will be ignored once released.
+ *
+ * @see #release
+ * @see Callback#onReleased
+ */
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
+ /**
+ * Add routes to the remote session.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void addRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Remove routes from this session. Media may be stopped on those devices.
+ * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void removeRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Registers a {@link Callback} for monitoring route changes.
+ * If the same callback is registered previously, previous executor will be overwritten with the
+ * new one.
+ */
+ public void registerCallback(Executor executor, Callback callback) {
+ if (mIsReleased) {
+ return;
+ }
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordWithSameCallback = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordWithSameCallback = record;
+ break;
+ }
+ }
+
+ if (recordWithSameCallback != null) {
+ recordWithSameCallback.mExecutor = executor;
+ } else {
+ mCallbackRecords.add(new CallbackRecord(executor, callback));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a previously registered {@link Callback}.
+ */
+ public void unregisterCallback(Callback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordToRemove = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordToRemove = record;
+ break;
+ }
+ }
+
+ if (recordToRemove != null) {
+ mCallbackRecords.remove(recordToRemove);
+ }
+ }
+ }
+
+ /**
+ * Release this session.
+ * Any operation on this session after calling this method will be ignored.
+ *
+ * @param stopMedia Should the device where the media is played
+ * be stopped after this session is released.
+ */
+ public void release(boolean stopMedia) {
+ mIsReleased = true;
+ mCallbackRecords.clear();
+ // TODO: Use stopMedia variable when the actual connection logic is implemented.
+ }
+
+ /**
+ * Callback class for getting updates on routes and session release.
+ */
+ public static class Callback {
+
+ /**
+ * Called when the session info has changed.
+ * TODO: When SessionInfo is introduced, uncomment below argument.
+ */
+ void onSessionInfoChanged(/* SessionInfo info */) {}
+
+ /**
+ * Called when the session is released. Session can be released by the controller using
+ * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
+ * One can do clean-ups here.
+ *
+ * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
+ * provider side.
+ */
+ void onReleased(int reason, boolean shouldStop) {}
+ }
+
+ private class CallbackRecord {
+ public final Callback mCallback;
+ public Executor mExecutor;
+
+ CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+}
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 494c61721e02..e17a6173ba4d 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -23,6 +23,7 @@
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
// This source file is compiled and linked into:
// core/jni/ (libandroid_runtime.so)
@@ -124,6 +125,28 @@ jobject MediaMetricsJNI::writeMetricsToBundle(
return bh.bundle;
}
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+ JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+ const jbyte* buffer =
+ reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+ if (buffer == nullptr) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return (jint)BAD_VALUE;
+ }
+
+ // TODO: directly record item to MediaMetrics service.
+ mediametrics::Item item;
+ if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
+ ALOGW("%s: cannot read from byte string", __func__);
+ return (jint)BAD_VALUE;
+ }
+ item.selfrecord();
+ return (jint)NO_ERROR;
+}
+
// Helper function to convert a native PersistableBundle to a Java
// PersistableBundle.
jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +214,18 @@ jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
return newBundle;
}
-}; // namespace android
+// ----------------------------------------------------------------------------
+static constexpr JNINativeMethod gMethods[] = {
+ {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(
+ env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+}; // namespace android
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 2c60d6b3dec7..326628587837 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,7 +16,15 @@
package com.android.mediaroutertest;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
+import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -24,20 +32,37 @@ import android.media.MediaRouter2;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MediaRouter2Test {
+ private static final String TAG = "MediaRouter2Test";
Context mContext;
+ private MediaRouter2 mRouter2;
+ private Executor mExecutor;
+
+ private static final int TIMEOUT_MS = 5000;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
+ mRouter2 = MediaRouter2.getInstance(mContext);
+ mExecutor = Executors.newSingleThreadExecutor();
}
@After
@@ -50,4 +75,95 @@ public class MediaRouter2Test {
MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
assertNotNull(initiallySelectedRoute);
}
+
+ /**
+ * Tests if we get proper routes for application that has special control category.
+ */
+ @Test
+ public void testGetRoutes() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+
+ assertEquals(1, routes.size());
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+ }
+
+ @Test
+ public void testControlVolumeWithRouter() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ assertNotNull(volRoute);
+
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume + deltaVolume));
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestSetVolume(volRoute, originalVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume));
+ }
+
+
+ // Helper for getting routes easily
+ static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+ Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+ for (MediaRoute2Info route : routes) {
+ // intentionally not using route.getUniqueId() for convenience.
+ routeMap.put(route.getId(), route);
+ }
+ return routeMap;
+ }
+
+ Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+ throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // A dummy callback is required to send control category info.
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ }
+ }
+ }
+ };
+
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, routerCallback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mRouter2.getRoutes());
+ } finally {
+ mRouter2.unregisterCallback(routerCallback);
+ }
+ }
+
+ void awaitOnRouteChanged(Runnable task, String routeId,
+ Predicate<MediaRoute2Info> predicate) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> changed) {
+ MediaRoute2Info route = createRouteMap(changed).get(routeId);
+ if (route != null && predicate.test(route)) {
+ latch.countDown();
+ }
+ }
+ };
+ mRouter2.registerCallback(mExecutor, callback);
+ try {
+ task.run();
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mRouter2.unregisterCallback(callback);
+ }
+ }
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c70ad8d8755c..b380aff20334 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -64,6 +64,9 @@ public class MediaRouterManagerTest {
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final String SYSTEM_PROVIDER_ID =
+ "com.android.server.media/.SystemMediaRoute2Provider";
+
public static final int VOLUME_MAX = 100;
public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
@@ -78,10 +81,7 @@ public class MediaRouterManagerTest {
public static final String CATEGORY_SPECIAL =
"com.android.mediarouteprovider.CATEGORY_SPECIAL";
- // system routes
- private static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
- private static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
private static final int TIMEOUT_MS = 5000;
@@ -93,10 +93,9 @@ public class MediaRouterManagerTest {
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
- private Map<String, MediaRoute2Info> mRoutes;
- private static final List<String> CATEGORIES_ALL = new ArrayList();
- private static final List<String> CATEGORIES_SPECIAL = new ArrayList();
+ public static final List<String> CATEGORIES_ALL = new ArrayList();
+ public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
static {
@@ -109,7 +108,6 @@ public class MediaRouterManagerTest {
CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
}
-
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
@@ -118,10 +116,6 @@ public class MediaRouterManagerTest {
//TODO: If we need to support thread pool executors, change this to thread pool executor.
mExecutor = Executors.newSingleThreadExecutor();
mPackageName = mContext.getPackageName();
-
- // ensure media router 2 client
- addRouterCallback(new MediaRouter2.Callback());
- mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
}
@After
@@ -168,6 +162,9 @@ public class MediaRouterManagerTest {
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -182,7 +179,7 @@ public class MediaRouterManagerTest {
//TODO: Figure out a more proper way to test.
// (Control requests shouldn't be used in this way.)
- mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
+ mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -198,23 +195,15 @@ public class MediaRouterManagerTest {
}
/**
- * Tests if we get proper routes for application that has special control category.
- */
- @Test
- public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
-
- assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
- }
-
- /**
* Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
*/
@Test
public void testRouterOnRouteSelected() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
CountDownLatch latch = new CountDownLatch(1);
+ addManagerCallback(new MediaRouter2Manager.Callback());
addRouterCallback(new MediaRouter2.Callback() {
@Override
public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
@@ -224,12 +213,16 @@ public class MediaRouterManagerTest {
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
}
/**
@@ -239,7 +232,9 @@ public class MediaRouterManagerTest {
@Test
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -250,12 +245,15 @@ public class MediaRouterManagerTest {
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
-
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
}
/**
@@ -263,13 +261,16 @@ public class MediaRouterManagerTest {
*/
@Test
public void testSingleProviderSelect() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
+
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
ROUTE_ID2,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -280,27 +281,10 @@ public class MediaRouterManagerTest {
}
@Test
- public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
-
+ public void testControlVolumeWithManager() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
- int originalVolume = volRoute.getVolume();
- int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
-
- awaitOnRouteChanged(
- () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume + deltaVolume));
- awaitOnRouteChanged(
- () -> mRouter2.requestSetVolume(volRoute, originalVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume));
- }
-
- @Test
- public void testControlVolumeWithManager() throws Exception {
- MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
int originalVolume = volRoute.getVolume();
int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
@@ -317,39 +301,16 @@ public class MediaRouterManagerTest {
@Test
public void testVolumeHandling() throws Exception {
- MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
- MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
+ MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- @Test
- public void testDefaultRoute() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_LIVE_AUDIO);
-
- assertNotNull(routes.get(DEFAULT_ROUTE_ID));
- }
-
- Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> added) {
- if (added.size() > 0) latch.countDown();
- }
- };
- mRouter2.setControlCategories(controlCategories);
- mRouter2.registerCallback(mExecutor, callback);
- try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- return createRouteMap(mRouter2.getRoutes());
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
@@ -359,13 +320,17 @@ public class MediaRouterManagerTest {
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
- if (routes.size() > 0) {
- latch.countDown();
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ break;
+ }
}
}
@Override
- public void onControlCategoriesChanged(String packageName) {
+ public void onControlCategoriesChanged(String packageName, List<String> categories) {
if (TextUtils.equals(mPackageName, packageName)) {
latch.countDown();
}
@@ -375,7 +340,7 @@ public class MediaRouterManagerTest {
mRouter2.setControlCategories(controlCategories);
mRouter2.registerCallback(mExecutor, routerCallback);
try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
} finally {
mRouter2.unregisterCallback(routerCallback);
@@ -383,27 +348,6 @@ public class MediaRouterManagerTest {
}
}
- void awaitOnRouteChanged(Runnable task, String routeId,
- Predicate<MediaRoute2Info> predicate) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesChanged(List<MediaRoute2Info> changed) {
- MediaRoute2Info route = createRouteMap(changed).get(routeId);
- if (route != null && predicate.test(route)) {
- latch.countDown();
- }
- }
- };
- mRouter2.registerCallback(mExecutor, callback);
- try {
- task.run();
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 14f233d958e6..2c001b033311 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,5 @@
package com.android.settingslib;
-import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-
import android.annotation.ColorInt;
import android.content.Context;
import android.content.Intent;
@@ -25,6 +23,8 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import com.android.internal.annotations.VisibleForTesting;
@@ -412,15 +412,30 @@ public class Utils {
// service" or "emergency calls only" text that indicates that voice
// is not available. Note that we ignore the IWLAN service state
// because that state indicates the use of VoWIFI and not cell service
- int state = serviceState.getState();
- int dataState = serviceState.getDataRegState();
+ final int state = serviceState.getState();
+ final int dataState = serviceState.getDataRegState();
+
if (state == ServiceState.STATE_OUT_OF_SERVICE
|| state == ServiceState.STATE_EMERGENCY_ONLY) {
- if (dataState == ServiceState.STATE_IN_SERVICE
- && serviceState.getDataNetworkType() != RIL_RADIO_TECHNOLOGY_IWLAN) {
+ if (dataState == ServiceState.STATE_IN_SERVICE && isNotInIwlan(serviceState)) {
return ServiceState.STATE_IN_SERVICE;
}
}
return state;
}
+
+ private static boolean isNotInIwlan(ServiceState serviceState) {
+ final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+ if (networkRegWlan == null) {
+ return true;
+ }
+
+ final boolean isInIwlan = (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ || (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+ return !isInIwlan;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index fc69b1a657d4..f18ffe1ebdd4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -34,6 +34,8 @@ import android.media.AudioManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.text.TextUtils;
@@ -69,6 +71,8 @@ public class UtilsTest {
private LocationManager mLocationManager;
@Mock
private ServiceState mServiceState;
+ @Mock
+ private NetworkRegistrationInfo mNetworkRegistrationInfo;
@Before
public void setUp() {
@@ -207,6 +211,7 @@ public class UtilsTest {
@Test
public void isInService_voiceInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@@ -214,15 +219,23 @@ public class UtilsTest {
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -230,12 +243,14 @@ public class UtilsTest {
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@Test
public void isInService_ServiceStatePowerOff_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -248,6 +263,7 @@ public class UtilsTest {
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_POWER_OFF);
}
@@ -255,6 +271,7 @@ public class UtilsTest {
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@@ -263,14 +280,33 @@ public class UtilsTest {
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@Test
+ public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
+ when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+ assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
+ ServiceState.STATE_OUT_OF_SERVICE);
+ }
+
+ @Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_OUT_OF_SERVICE);
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5d0db01cd5ae..19ff2444e6ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -215,7 +215,6 @@ public class SettingsBackupTest {
Settings.Global.DEFAULT_DNS_SERVER,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
- Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
Settings.Global.DESK_DOCK_SOUND,
Settings.Global.DESK_UNDOCK_SOUND,
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0407ffe3ea7c..45318fde4f0e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -915,7 +915,7 @@
<!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
<!-- QuickSettings: Secondary text for the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
- <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery saver</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery Saver</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
diff --git a/services/Android.bp b/services/Android.bp
index 8376d2bcc0f5..8be6e16c8e5c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -117,9 +117,6 @@ droidstubs {
" --hide ReferencesHidden" +
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
- libs: [
- "framework-all",
- ],
visibility: ["//visibility:private"],
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 49046b244bb4..d2f1113ec5a9 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -133,6 +133,12 @@ public abstract class PackageManagerInternal {
@PackageInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Retrieve CE data directory inode number of an application.
+ * Return 0 if there's error.
+ */
+ public abstract long getCeDataInode(String packageName, int userId);
+
+ /**
* Return a List of all application packages that are installed on the
* device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
* set, a list of all applications including those deleted with
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5a78036911a8..22fa8ff4a0fa 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -144,6 +144,7 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
@@ -367,6 +368,8 @@ class StorageManagerService extends IStorageManager.Stub
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ private final Installer mInstaller;
+
/** Holding lock for AppFuse business */
private final Object mAppFuseLock = new Object();
@@ -1245,6 +1248,13 @@ class StorageManagerService extends IStorageManager.Stub
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
+ mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume mounted " + vol, e);
+ }
}
}
@@ -1290,6 +1300,13 @@ class StorageManagerService extends IStorageManager.Stub
if (vol != null) {
mStorageSessionController.onVolumeRemove(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume unmounted " + vol, e);
+ }
}
}
};
@@ -1601,6 +1618,9 @@ class StorageManagerService extends IStorageManager.Stub
mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled);
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
+
// Initialize the last-fstrim tracking if necessary
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
@@ -1974,6 +1994,13 @@ class StorageManagerService extends IStorageManager.Stub
try {
mVold.unmount(vol.id);
mStorageSessionController.onVolumeUnmount(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.e(TAG, "Failed unmount mirror data", e);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index f6c11cd1d0d6..76a8f92312ae 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,6 +61,7 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
@@ -162,6 +163,8 @@ public class VibratorService extends IVibratorService.Stub
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
+ private SparseArray<Pair<VibrationEffect, AudioAttributes>> mAlwaysOnEffects =
+ new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -173,6 +176,8 @@ public class VibratorService extends IVibratorService.Stub
static native boolean vibratorSupportsExternalControl();
static native void vibratorSetExternalControl(boolean enabled);
static native long vibratorGetCapabilities();
+ static native void vibratorAlwaysOnEnable(long id, long effect, long strength);
+ static native void vibratorAlwaysOnDisable(long id);
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -522,6 +527,41 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ @Override // Binder call
+ public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attrs) {
+ if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
+ throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
+ }
+ if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) {
+ Slog.e(TAG, "Always-on effects not supported.");
+ return false;
+ }
+ if (effect == null) {
+ synchronized (mLock) {
+ mAlwaysOnEffects.delete(id);
+ vibratorAlwaysOnDisable(id);
+ }
+ } else {
+ if (!verifyVibrationEffect(effect)) {
+ return false;
+ }
+ if (!(effect instanceof VibrationEffect.Prebaked)) {
+ Slog.e(TAG, "Only prebaked effects supported for always-on.");
+ return false;
+ }
+ if (attrs == null) {
+ attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_UNKNOWN)
+ .build();
+ }
+ synchronized (mLock) {
+ mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
+ updateAlwaysOnLocked(id, effect, attrs);
+ }
+ }
+ return true;
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -992,6 +1032,8 @@ public class VibratorService extends IVibratorService.Stub
// If the state changes out from under us then just reset.
doCancelVibrateLocked();
}
+
+ updateAlwaysOnLocked();
}
}
@@ -1058,6 +1100,27 @@ public class VibratorService extends IVibratorService.Stub
mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
+ private void updateAlwaysOnLocked(int id, VibrationEffect effect, AudioAttributes attrs) {
+ // TODO: Check DND and LowPower settings
+ final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+ final int intensity = getCurrentIntensityLocked(vib);
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ vibratorAlwaysOnDisable(id);
+ } else {
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final int strength = intensityToEffectStrength(intensity);
+ vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
+ }
+ }
+
+ private void updateAlwaysOnLocked() {
+ for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
+ int id = mAlwaysOnEffects.keyAt(i);
+ Pair<VibrationEffect, AudioAttributes> pair = mAlwaysOnEffects.valueAt(i);
+ updateAlwaysOnLocked(id, pair.first, pair.second);
+ }
+ }
+
@Override
public void onInputDeviceAdded(int deviceId) {
updateVibrators();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5378f438fea5..557def44dc66 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.AppZygote;
@@ -78,6 +79,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -85,6 +87,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -117,6 +120,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import java.util.Map;
/**
* Activity manager code dealing with processes.
@@ -124,6 +128,13 @@ import java.util.List;
public final class ProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
static final int MIN_CRASH_INTERVAL = 60 * 1000;
@@ -337,6 +348,8 @@ public final class ProcessList {
private boolean mOomLevelsSet = false;
+ private boolean mAppDataIsolationEnabled = false;
+
/**
* Temporary to avoid allocations. Protected by main lock.
*/
@@ -461,9 +474,7 @@ public final class ProcessList {
@GuardedBy("ProcessList.this.mService")
void freeIsolatedUidLocked(int uid) {
- // Strip out userId
- final int appId = UserHandle.getAppId(uid);
- mUidUsed.delete(appId);
+ mUidUsed.delete(uid);
}
};
@@ -623,6 +634,10 @@ public final class ProcessList {
mService = service;
mActiveUids = activeUids;
mPlatformCompat = platformCompat;
+ // Get this after boot, and won't be changed until it's rebooted, as we don't
+ // want some apps enabled while some apps disabled
+ mAppDataIsolationEnabled =
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -1855,6 +1870,32 @@ public final class ProcessList {
}
}
+ private boolean shouldIsolateAppData(ProcessRecord app) {
+ if (!mAppDataIsolationEnabled) {
+ return false;
+ }
+ if (!UserHandle.isApp(app.uid)) {
+ return false;
+ }
+ final int minTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ANDROID_APP_DATA_ISOLATION_MIN_SDK, Build.VERSION_CODES.R);
+ return app.info.targetSdkVersion >= minTargetSdk;
+ }
+
+ private Map<String, Pair<String, Long>> getPackageAppDataInfoMap(PackageManagerInternal pmInt,
+ String[] packages, int uid) {
+ Map<String, Pair<String, Long>> result = new ArrayMap<>(packages.length);
+ int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+ long inode = pmInt.getCeDataInode(packageName, userId);
+ if (inode != 0) {
+ result.put(packageName, Pair.create(volumeUuid, inode));
+ }
+ }
+ return result;
+ }
+
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1871,6 +1912,21 @@ public final class ProcessList {
app.setHasForegroundActivities(true);
}
+ final Map<String, Pair<String, Long>> pkgDataInfoMap;
+
+ if (shouldIsolateAppData(app)) {
+ // Get all packages belongs to the same shared uid. sharedPackages is empty array
+ // if it doesn't have shared uid.
+ final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+ final String sharedUserId = pmInt.getSharedUserIdForPackage(app.info.packageName);
+ final String[] sharedPackages = pmInt.getPackagesForSharedUserId(sharedUserId,
+ app.userId);
+ pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages, uid);
+ } else {
+ pkgDataInfoMap = null;
+ }
+
final Process.ProcessStartResult startResult;
if (hostingRecord.usesWebviewZygote()) {
startResult = startWebView(entryPoint,
@@ -1886,13 +1942,13 @@ public final class ProcessList {
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
- app.mDisabledCompatChanges,
+ app.mDisabledCompatChanges, pkgDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2de18c391d4a..60f0e8e6c63d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -1013,8 +1014,13 @@ public abstract class BiometricServiceBase extends SystemService
private boolean isForegroundActivity(int uid, int pid) {
try {
- List<ActivityManager.RunningAppProcessInfo> procs =
+ final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(getTag(), "Processes null, defaulting to true");
+ return true;
+ }
+
int N = procs.size();
for (int i = 0; i < N; i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
@@ -1206,6 +1212,11 @@ public abstract class BiometricServiceBase extends SystemService
* @return authenticator id for the calling user
*/
protected long getAuthenticatorId(String opPackageName) {
+ if (isKeyguard(opPackageName)) {
+ // If an app tells us it's keyguard, check that it actually is.
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ }
+
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
return mAuthenticatorIds.getOrDefault(userId, 0L);
}
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
index 019aa4fb0f2b..55a4e409c767 100644
--- a/services/core/java/com/android/server/integrity/OWNERS
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -3,4 +3,3 @@ khelmy@google.com
mdchurchill@google.com
sturla@google.com
songpan@google.com
-bjy@google.com
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java
deleted file mode 100644
index 4d3961df6092..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexTypeIdentifier.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 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.integrity.serializer;
-
-import android.annotation.IntDef;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.Formula;
-import android.content.integrity.Rule;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/** A helper class for identifying the indexing type of a given rule. */
-public class RuleIndexTypeIdentifier {
-
- static final int NOT_INDEXED = 0;
- static final int PACKAGE_NAME_INDEXED = 1;
- static final int APP_CERTIFICATE_INDEXED = 2;
-
- /** Represents which indexed file the rule should be located. */
- @IntDef(
- value = {
- NOT_INDEXED,
- PACKAGE_NAME_INDEXED,
- APP_CERTIFICATE_INDEXED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface IndexType {
- }
-
- /** Determines the indexing file type that a given rule should be located at. */
- public static int getIndexType(Rule rule) {
- if (rule == null) {
- throw new IllegalArgumentException("Indexing type cannot be determined for null rule.");
- }
- return getIndexType(rule.getFormula());
- }
-
- private static int getIndexType(Formula formula) {
- if (formula == null) {
- throw new IllegalArgumentException(
- "Indexing type cannot be determined for null formula.");
- }
-
- switch (formula.getTag()) {
- case Formula.COMPOUND_FORMULA_TAG:
- return getIndexTypeForCompoundFormula((CompoundFormula) formula);
- case Formula.STRING_ATOMIC_FORMULA_TAG:
- return getIndexTypeForAtomicStringFormula((AtomicFormula) formula);
- case Formula.INT_ATOMIC_FORMULA_TAG:
- case Formula.BOOLEAN_ATOMIC_FORMULA_TAG:
- // Package name and app certificate related formulas are string atomic formulas.
- return NOT_INDEXED;
- default:
- throw new IllegalArgumentException(
- String.format("Invalid formula tag type: %s", formula.getTag()));
- }
- }
-
- private static int getIndexTypeForCompoundFormula(CompoundFormula compoundFormula) {
- int connector = compoundFormula.getConnector();
- List<Formula> formulas = compoundFormula.getFormulas();
-
- switch (connector) {
- case CompoundFormula.NOT:
- // Having a NOT operator in the indexing messes up the indexing; e.g., deny
- // installation if app certificate is NOT X (should not be indexed with app cert
- // X). We will not keep these rules indexed.
- return NOT_INDEXED;
- case CompoundFormula.AND:
- case CompoundFormula.OR:
- Set<Integer> indexingTypesForAllFormulas =
- formulas.stream()
- .map(formula -> getIndexType(formula))
- .collect(Collectors.toSet());
- if (indexingTypesForAllFormulas.contains(PACKAGE_NAME_INDEXED)) {
- return PACKAGE_NAME_INDEXED;
- } else if (indexingTypesForAllFormulas.contains(APP_CERTIFICATE_INDEXED)) {
- return APP_CERTIFICATE_INDEXED;
- } else {
- return NOT_INDEXED;
- }
- default:
- return NOT_INDEXED;
- }
- }
-
- private static int getIndexTypeForAtomicStringFormula(AtomicFormula atomicFormula) {
- switch (atomicFormula.getKey()) {
- case AtomicFormula.PACKAGE_NAME:
- return PACKAGE_NAME_INDEXED;
- case AtomicFormula.APP_CERTIFICATE:
- return APP_CERTIFICATE_INDEXED;
- default:
- return NOT_INDEXED;
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
new file mode 100644
index 000000000000..dd871e2bbe6c
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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.integrity.serializer;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Holds the indexing type and indexing key of a given formula. */
+class RuleIndexingDetails {
+
+ static final int NOT_INDEXED = 0;
+ static final int PACKAGE_NAME_INDEXED = 1;
+ static final int APP_CERTIFICATE_INDEXED = 2;
+
+ /** Represents which indexed file the rule should be located. */
+ @IntDef(
+ value = {
+ NOT_INDEXED,
+ PACKAGE_NAME_INDEXED,
+ APP_CERTIFICATE_INDEXED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndexType {
+ }
+
+ private @IndexType int mIndexType;
+ private String mRuleKey;
+
+ /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
+ RuleIndexingDetails(@IndexType int indexType) {
+ this.mIndexType = indexType;
+ this.mRuleKey = null;
+ }
+
+ /** Constructor with a ruleKey for indexed rules. */
+ RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
+ this.mIndexType = indexType;
+ this.mRuleKey = ruleKey;
+ }
+
+ /** Returns the indexing type for the rule. */
+ @IndexType
+ public int getIndexType() {
+ return mIndexType;
+ }
+
+ /** Returns the identified rule key. */
+ public String getRuleKey() {
+ return mRuleKey;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
new file mode 100644
index 000000000000..4aabb1c87b34
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 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.integrity.serializer;
+
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
+
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import java.util.List;
+import java.util.Optional;
+
+/** A helper class for identifying the indexing type and key of a given rule. */
+class RuleIndexingDetailsIdentifier {
+
+ /** Determines the indexing type and key for a given rule. */
+ public static RuleIndexingDetails getIndexingDetails(Rule rule) {
+ if (rule == null) {
+ throw new IllegalArgumentException("Indexing type cannot be determined for null rule.");
+ }
+ return getIndexingDetails(rule.getFormula());
+ }
+
+ private static RuleIndexingDetails getIndexingDetails(Formula formula) {
+ if (formula == null) {
+ throw new IllegalArgumentException(
+ "Indexing type cannot be determined for null formula.");
+ }
+
+ switch (formula.getTag()) {
+ case Formula.COMPOUND_FORMULA_TAG:
+ return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
+ case Formula.STRING_ATOMIC_FORMULA_TAG:
+ return getIndexingDetailsForAtomicStringFormula((AtomicFormula) formula);
+ case Formula.INT_ATOMIC_FORMULA_TAG:
+ case Formula.BOOLEAN_ATOMIC_FORMULA_TAG:
+ // Package name and app certificate related formulas are string atomic formulas.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ throw new IllegalArgumentException(
+ String.format("Invalid formula tag type: %s", formula.getTag()));
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
+ CompoundFormula compoundFormula) {
+ int connector = compoundFormula.getConnector();
+ List<Formula> formulas = compoundFormula.getFormulas();
+
+ switch (connector) {
+ case CompoundFormula.AND:
+ case CompoundFormula.OR:
+ // If there is a package name related atomic rule, return package name indexed.
+ Optional<RuleIndexingDetails> packageNameRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == PACKAGE_NAME_INDEXED)
+ .findAny();
+ if (packageNameRule.isPresent()) {
+ return packageNameRule.get();
+ }
+
+ // If there is an app certificate related atomic rule but no package name related
+ // atomic rule, return app certificate indexed.
+ Optional<RuleIndexingDetails> appCertificateRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == APP_CERTIFICATE_INDEXED)
+ .findAny();
+ if (appCertificateRule.isPresent()) {
+ return appCertificateRule.get();
+ }
+
+ // Do not index when there is not package name or app certificate indexing.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ // Having a NOT operator in the indexing messes up the indexing; e.g., deny
+ // installation if app certificate is NOT X (should not be indexed with app cert
+ // X). We will not keep these rules indexed.
+ // Also any other type of unknown operators will not be indexed.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForAtomicStringFormula(
+ AtomicFormula atomicFormula) {
+ switch (atomicFormula.getKey()) {
+ case AtomicFormula.PACKAGE_NAME:
+ return new RuleIndexingDetails(PACKAGE_NAME_INDEXED,
+ getValueFromAtomicRuleString(atomicFormula.toString()));
+ case AtomicFormula.APP_CERTIFICATE:
+ return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED,
+ getValueFromAtomicRuleString(atomicFormula.toString()));
+ default:
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+
+ // The AtomRule API does not allow direct access to the {@link AtomicFormula} value. However,
+ // this value is printed as "(%s %s %s)" where the last %s stands for the value. This method
+ // parses the last.
+ private static String getValueFromAtomicRuleString(String ruleString) {
+ // TODO (b/145488708): Make an API change and get rid of this trick.
+ return ruleString.split(" ")[2].split("[)]")[0];
+ }
+}
+
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9fcee50d7037..e7b88604db32 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -183,8 +183,9 @@ class MediaRouter2ServiceImpl {
}
public void setControlCategories(@NonNull IMediaRouter2Client client,
- @Nullable List<String> categories) {
+ @NonNull List<String> categories) {
Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(categories, "categories must not be null");
final long token = Binder.clearCallingIdentity();
try {
@@ -390,8 +391,11 @@ class MediaRouter2ServiceImpl {
private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
if (clientRecord != null) {
- clientRecord.mControlCategories = categories;
+ if (clientRecord.mControlCategories.equals(categories)) {
+ return;
+ }
+ clientRecord.mControlCategories = categories;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 26cd42daa9f8..eb4b5939aaad 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -580,6 +580,30 @@ public class Installer extends SystemService {
}
}
+ /**
+ * Bind mount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeMounted(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Unmount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeRemoved(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeRemoved(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
if (!checkBeforeRemote()) return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 99d5e4a21df4..cb362b0f0a23 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -22626,6 +22626,18 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public long getCeDataInode(String packageName, int userId) {
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ return ps.getCeDataInode(userId);
+ }
+ Slog.e(TAG, "failed to find package " + packageName);
+ return 0;
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
synchronized (mLock) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b4d80531be54..b3013c7e0a5f 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -385,7 +385,7 @@ public class TimeZoneDetectorStrategy {
}
private static boolean isOriginAutomatic(@Origin int origin) {
- return origin == ORIGIN_PHONE;
+ return origin != ORIGIN_MANUAL;
}
@GuardedBy("this")
@@ -456,15 +456,17 @@ public class TimeZoneDetectorStrategy {
* Dumps internal state such as field values.
*/
public synchronized void dumpState(PrintWriter pw, String[] args) {
- pw.println("TimeZoneDetectorStrategy:");
- pw.println("mCallback.isTimeZoneDetectionEnabled()="
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TimeZoneDetectorStrategy:");
+
+ ipw.increaseIndent(); // level 1
+ ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+ mCallback.isAutoTimeZoneDetectionEnabled());
- pw.println("mCallback.isDeviceTimeZoneInitialized()="
+ ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
- pw.println("mCallback.getDeviceTimeZone()="
+ ipw.println("mCallback.getDeviceTimeZone()="
+ mCallback.getDeviceTimeZone());
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
mTimeZoneChangesLog.dump(ipw);
@@ -485,8 +487,6 @@ public class TimeZoneDetectorStrategy {
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
ipw.flush();
-
- pw.flush();
}
/**
diff --git a/services/core/java/com/android/server/utils/TEST_MAPPING b/services/core/java/com/android/server/utils/TEST_MAPPING
new file mode 100644
index 000000000000..bb7cea98eda0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.utils"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
new file mode 100644
index 000000000000..7b499139aa7c
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.SparseArrayMap;
+
+import java.util.function.Consumer;
+
+/**
+ * A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
+ * (UPTC)->object associations. Tags are any desired String.
+ *
+ * @see Uptc
+ */
+class UptcMap<T> {
+ private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
+
+ public void add(int userId, @NonNull String packageName, @Nullable String tag,
+ @Nullable T obj) {
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data == null) {
+ data = new ArrayMap<>();
+ mData.add(userId, packageName, data);
+ }
+ data.put(tag, obj);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ public boolean contains(int userId, @NonNull String packageName) {
+ return mData.contains(userId, packageName);
+ }
+
+ public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
+ // This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
+ // null, the UPTC was never inserted.
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null && data.containsKey(tag);
+ }
+
+ /** Removes all the data for the user, if there was any. */
+ public void delete(int userId) {
+ mData.delete(userId);
+ }
+
+ /** Removes the data for the user, package, and tag, if there was any. */
+ public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data != null) {
+ data.remove(tag);
+ if (data.size() == 0) {
+ mData.delete(userId, packageName);
+ }
+ }
+ }
+
+ /** Removes the data for the user and package, if there was any. */
+ public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
+ return mData.delete(userId, packageName);
+ }
+
+ /**
+ * Returns the set of tag -> object mappings for the given userId and packageName
+ * combination.
+ */
+ @Nullable
+ public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
+ return mData.get(userId, packageName);
+ }
+
+ /** Returns the saved object for the given UPTC. */
+ @Nullable
+ public T get(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null ? data.get(tag) : null;
+ }
+
+ /**
+ * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
+ * or a negative number if the specified userId is not mapped.
+ */
+ public int indexOfUserId(int userId) {
+ return mData.indexOfKey(userId);
+ }
+
+ /**
+ * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
+ * specified userId, or a negative number if the specified userId and packageName are not mapped
+ * together.
+ */
+ public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
+ return mData.indexOfKey(userId, packageName);
+ }
+
+ /** Returns the userId at the given index. */
+ public int getUserIdAtIndex(int index) {
+ return mData.keyAt(index);
+ }
+
+ /** Returns the package name at the given index. */
+ @NonNull
+ public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+ return mData.keyAt(userIndex, packageIndex);
+ }
+
+ /** Returns the tag at the given index. */
+ @NonNull
+ public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+ // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
+ // won't return null.
+ return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
+ }
+
+ /** Returns the size of the outer (userId) array. */
+ public int userCount() {
+ return mData.numMaps();
+ }
+
+ /** Returns the number of packages saved for a given userId. */
+ public int packageCountForUser(int userId) {
+ return mData.numElementsForKey(userId);
+ }
+
+ /** Returns the number of tags saved for a given userId-packageName combination. */
+ public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
+ final ArrayMap data = mData.get(userId, packageName);
+ return data != null ? data.size() : 0;
+ }
+
+ /** Returns the value T at the given user, package, and tag indices. */
+ @Nullable
+ public T valueAt(int userIndex, int packageIndex, int tagIndex) {
+ final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
+ return data != null ? data.valueAt(tagIndex) : null;
+ }
+
+ public void forEach(Consumer<T> consumer) {
+ mData.forEach((tagMap) -> {
+ for (int i = tagMap.size() - 1; i >= 0; --i) {
+ consumer.accept(tagMap.valueAt(i));
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e3ea2c5f1ac4..4667eab1f55b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4955,7 +4955,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// Re-parent IME's SurfaceControl when MagnificationSpec changed.
updateImeParent();
- applyMagnificationSpec(getPendingTransaction(), spec);
+ if (spec.scale != 1.0) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ } else {
+ clearMagnificationSpec(getPendingTransaction());
+ }
getPendingTransaction().apply();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d73cb50fe107..c95111fd4927 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -41,7 +41,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.logWithStack;
-import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
@@ -70,7 +69,6 @@ import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.policy.WindowManagerPolicy;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -244,6 +242,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
+ private MagnificationSpec mLastMagnificationSpec;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -1726,6 +1726,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ mLastMagnificationSpec = spec;
} else {
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
@@ -1733,6 +1734,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ void clearMagnificationSpec(Transaction t) {
+ if (mLastMagnificationSpec != null) {
+ t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
+ .setPosition(mSurfaceControl, 0, 0);
+ }
+ mLastMagnificationSpec = null;
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).clearMagnificationSpec(t);
+ }
+ }
+
void prepareSurfaces() {
// If a leash has been set when the transaction was committed, then the leash reparent has
// been committed.
@@ -2151,15 +2163,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
void waitForAllWindowsDrawn() {
- final WindowManagerPolicy policy = mWmService.mPolicy;
forAllWindows(w -> {
- final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
- if (w.isVisibleLw() && (w.mActivityRecord != null || keyguard)) {
- w.mWinAnimator.mDrawState = DRAW_PENDING;
- // Force add to mResizingWindows.
- w.resetLastContentInsets();
- mWaitingForDrawn.add(w);
- }
+ w.requestDrawIfNeeded(mWaitingForDrawn);
}, true /* traverseTopToBottom */);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6918c966a3ec..2a8de3f95f46 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1726,6 +1726,39 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& isDrawnLw() && !isAnimating(TRANSITION | PARENTS);
}
+ /** @see WindowManagerInternal#waitForAllWindowsDrawn */
+ void requestDrawIfNeeded(List<WindowState> outWaitingForDrawn) {
+ if (!isVisible()) {
+ return;
+ }
+ if (mActivityRecord != null) {
+ if (mActivityRecord.allDrawn) {
+ // The allDrawn of activity is reset when the visibility is changed to visible, so
+ // the content should be ready if allDrawn is set.
+ return;
+ }
+ if (mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (isDrawnLw()) {
+ // Unnecessary to redraw a drawn starting window.
+ return;
+ }
+ } else if (mActivityRecord.startingWindow != null) {
+ // If the activity has an active starting window, there is no need to wait for the
+ // main window.
+ return;
+ }
+ } else if (!mPolicy.isKeyguardHostWindow(mAttrs)) {
+ return;
+ // Always invalidate keyguard host window to make sure it shows the latest content
+ // because its visibility may not be changed.
+ }
+
+ mWinAnimator.mDrawState = DRAW_PENDING;
+ // Force add to {@link WindowManagerService#mResizingWindows}.
+ resetLastContentInsets();
+ outWaitingForDrawn.add(this);
+ }
+
@Override
void onMovedByResize() {
ProtoLog.d(WM_DEBUG_RESIZE, "onMovedByResize: Moving %s", this);
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index dcff5a11aca0..6811e6d0e6f2 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -410,6 +410,21 @@ static jlong vibratorGetCapabilities(JNIEnv*, jclass) {
return 0;
}
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass, jlong id, jlong effect, jlong strength) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnEnable, id,
+ static_cast<aidl::Effect>(effect), static_cast<aidl::EffectStrength>(strength));
+ if (!status.isOk()) {
+ ALOGE("vibratortAlwaysOnEnable command failed (%s).", status.toString8().string());
+ }
+}
+
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass, jlong id) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnDisable, id);
+ if (!status.isOk()) {
+ ALOGE("vibratorAlwaysOnDisable command failed (%s).", status.toString8().string());
+ }
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
@@ -422,6 +437,8 @@ static const JNINativeMethod method_table[] = {
{ "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
{ "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
{ "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
+ { "vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
+ { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7d6b0c99c5b5..a1e02370aa51 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -105,6 +105,7 @@ import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.gpu.GpuService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.incident.IncidentCompanionService;
+import com.android.server.incremental.IncrementalManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodSystemProperty;
@@ -324,6 +325,7 @@ public final class SystemServer {
private ContentResolver mContentResolver;
private EntropyMixer mEntropyMixer;
private DataLoaderManagerService mDataLoaderManagerService;
+ private IncrementalManagerService mIncrementalManagerService;
private boolean mOnlyCore;
private boolean mFirstBoot;
@@ -706,6 +708,11 @@ public final class SystemServer {
DataLoaderManagerService.class);
t.traceEnd();
+ // Incremental service needs to be started before package manager
+ t.traceBegin("StartIncrementalManagerService");
+ mIncrementalManagerService = IncrementalManagerService.start(mSystemContext);
+ t.traceEnd();
+
// Power manager needs to be started early because other services need it.
// Native daemons may be watching for it to be registered so it must be ready
// to handle incoming binder calls immediately (including being able to verify
@@ -2066,6 +2073,12 @@ public final class SystemServer {
mPackageManagerService.systemReady();
t.traceEnd();
+ if (mIncrementalManagerService != null) {
+ t.traceBegin("MakeIncrementalManagerServiceReady");
+ mIncrementalManagerService.systemReady();
+ t.traceEnd();
+ }
+
t.traceBegin("MakeDisplayManagerServiceReady");
try {
// TODO: use boot phase and communicate these flags some other way
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 49412bc0c559..2ce17a1d0cea 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -349,10 +349,21 @@ public class ActivityManagerServiceTest {
verifyUidRangesNoOverlap(range, range2);
verifyIsolatedUidAllocator(range2);
- // Free both, then try to allocate the maximum number of UID ranges
+ // Free both
allocator.freeUidRangeLocked(appInfo);
allocator.freeUidRangeLocked(appInfo2);
+ // Verify for a secondary user
+ ApplicationInfo appInfo3 = new ApplicationInfo();
+ appInfo3.processName = "com.android.test.app";
+ appInfo3.uid = 1010000;
+ final IsolatedUidRange range3 = allocator.getOrCreateIsolatedUidRangeLocked(
+ appInfo3.processName, appInfo3.uid);
+ validateAppZygoteIsolatedUidRange(range3);
+ verifyIsolatedUidAllocator(range3);
+
+ allocator.freeUidRangeLocked(appInfo3);
+ // Try to allocate the maximum number of UID ranges
int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID
- Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE;
for (int i = 0; i < maxNumUidRanges; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 18b91b0b1009..abb1787cede5 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexTypeIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -16,6 +16,9 @@
package com.android.server.integrity.serializer;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
import static com.android.server.testutils.TestUtils.assertExpectException;
import static com.google.common.truth.Truth.assertThat;
@@ -34,9 +37,9 @@ import org.junit.runners.JUnit4;
import java.util.Arrays;
-/** Unit tests for {@link RuleIndexTypeIdentifier}. */
+/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
@RunWith(JUnit4.class)
-public class RuleIndexTypeIdentifierTest {
+public class RuleIndexingDetailsIdentifierTest {
@Test
public void getIndexType_nullRule() {
@@ -46,7 +49,7 @@ public class RuleIndexTypeIdentifierTest {
IllegalArgumentException.class,
/* expectedExceptionMessageRegex= */
"Indexing type cannot be determined for null rule.",
- () -> RuleIndexTypeIdentifier.getIndexType(rule));
+ () -> RuleIndexingDetailsIdentifier.getIndexingDetails(rule));
}
@Test
@@ -56,7 +59,7 @@ public class RuleIndexTypeIdentifierTest {
assertExpectException(
IllegalArgumentException.class,
/* expectedExceptionMessageRegex= */ "Invalid formula tag type.",
- () -> RuleIndexTypeIdentifier.getIndexType(rule));
+ () -> RuleIndexingDetailsIdentifier.getIndexingDetails(rule));
}
@Test
@@ -78,8 +81,10 @@ public class RuleIndexTypeIdentifierTest {
/* isHashedValue= */ false))),
Rule.DENY);
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.PACKAGE_NAME_INDEXED);
+ RuleIndexingDetails result = RuleIndexingDetailsIdentifier.getIndexingDetails(rule);
+
+ assertThat(result.getIndexType()).isEqualTo(PACKAGE_NAME_INDEXED);
+ assertThat(result.getRuleKey()).isEqualTo(packageName);
}
@Test
@@ -101,8 +106,11 @@ public class RuleIndexTypeIdentifierTest {
/* isHashedValue= */ false))),
Rule.DENY);
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.APP_CERTIFICATE_INDEXED);
+
+ RuleIndexingDetails result = RuleIndexingDetailsIdentifier.getIndexingDetails(rule);
+
+ assertThat(result.getIndexType()).isEqualTo(APP_CERTIFICATE_INDEXED);
+ assertThat(result.getRuleKey()).isEqualTo(appCertificate);
}
@Test
@@ -124,8 +132,8 @@ public class RuleIndexTypeIdentifierTest {
/* isHashedValue= */ false))),
Rule.DENY);
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
+ assertThat(RuleIndexingDetailsIdentifier.getIndexingDetails(rule).getIndexType())
+ .isEqualTo(NOT_INDEXED);
}
@Test
@@ -145,8 +153,8 @@ public class RuleIndexTypeIdentifierTest {
appVersion))),
Rule.DENY);
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
+ assertThat(RuleIndexingDetailsIdentifier.getIndexingDetails(rule).getIndexType())
+ .isEqualTo(NOT_INDEXED);
}
@Test
@@ -171,8 +179,8 @@ public class RuleIndexTypeIdentifierTest {
/* isHashedValue= */ false))))),
Rule.DENY);
- assertThat(RuleIndexTypeIdentifier.getIndexType(rule))
- .isEqualTo(RuleIndexTypeIdentifier.NOT_INDEXED);
+ assertThat(RuleIndexingDetailsIdentifier.getIndexingDetails(rule).getIndexType())
+ .isEqualTo(NOT_INDEXED);
}
private Formula getInvalidFormula() {
@@ -215,4 +223,3 @@ public class RuleIndexTypeIdentifierTest {
};
}
}
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 8ade4d27e495..0018c633e675 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -32,7 +32,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -82,7 +84,9 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.LinkedList;
+import java.util.List;
/**
* Tests for the {@link WindowState} class.
@@ -532,6 +536,31 @@ public class WindowStateTests extends WindowTestsBase {
}
@Test
+ public void testRequestDrawIfNeeded() {
+ final WindowState startingApp = createWindow(null /* parent */,
+ TYPE_BASE_APPLICATION, "startingApp");
+ final WindowState startingWindow = createWindow(null /* parent */,
+ TYPE_APPLICATION_STARTING, startingApp.mToken, "starting");
+ startingApp.mActivityRecord.startingWindow = startingWindow;
+ final WindowState keyguardHostWindow = mStatusBarWindow;
+ final WindowState allDrawnApp = mAppWindow;
+ allDrawnApp.mActivityRecord.allDrawn = true;
+
+ // The waiting list is used to ensure the content is ready when turning on screen.
+ final List<WindowState> outWaitingForDrawn = mDisplayContent.mWaitingForDrawn;
+ final List<WindowState> visibleWindows = Arrays.asList(mChildAppWindowAbove,
+ keyguardHostWindow, allDrawnApp, startingApp, startingWindow);
+ visibleWindows.forEach(w -> {
+ w.mHasSurface = true;
+ w.requestDrawIfNeeded(outWaitingForDrawn);
+ });
+
+ // Keyguard host window should be always contained. The drawn app or app with starting
+ // window are unnecessary to draw.
+ assertEquals(Arrays.asList(keyguardHostWindow, startingWindow), outWaitingForDrawn);
+ }
+
+ @Test
public void testGetTransformationMatrix() {
final int PARENT_WINDOW_OFFSET = 1;
final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 616b6b005f89..248c117d2e03 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,7 +28,7 @@ java_sdk_library {
":framework_native_aidl",
],
libs: [
- "framework-all",
+ "framework",
"app-compat-annotations",
"unsupportedappusage",
],
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 0208c3a0a0de..9d913b9861e5 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -211,6 +212,15 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @Override
+ public File getCrateDir(@NonNull String crateId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public File getNoBackupFilesDir() {
throw new UnsupportedOperationException();