diff options
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(); |