Merge "Revert "Use VERSION_CODES.S instead of VERSION_CODES.R + 1"" am: aef89adfdc am: b331ee3ff7 am: 84047d72cf am: b84834df76
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1619903
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I3fcb639c714b5dba8070d04f471c5507f3b51f3d
diff --git a/api/Android.bp b/api/Android.bp
index 1d4698e..1fdf177 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -347,3 +347,49 @@
out: ["combined-removed-dex.txt"],
cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
}
+
+genrule {
+ name: "services-system-server-current.txt",
+ srcs: [
+ ":service-permission{.system-server.api.txt}",
+ ":non-updatable-system-server-current.txt",
+ ],
+ out: ["system-server-current.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+ dists: [
+ {
+ targets: ["droidcore"],
+ dir: "api",
+ dest: "system-server-current.txt",
+ },
+ {
+ targets: ["sdk", "win_sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "merge-android.txt",
+ },
+ ],
+}
+
+genrule {
+ name: "services-system-server-removed.txt",
+ srcs: [
+ ":service-permission{.system-server.removed-api.txt}",
+ ":non-updatable-system-server-removed.txt",
+ ],
+ out: ["system-server-removed.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+ dists: [
+ {
+ targets: ["droidcore"],
+ dir: "api",
+ dest: "system-server-removed.txt",
+ },
+ {
+ targets: ["sdk", "win_sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "merge-removed.txt",
+ },
+ ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index a06f994..b3c2244 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21157,7 +21157,7 @@
method @NonNull public String getDiagnosticInfo();
}
- public final class MediaCodec implements android.media.metrics.PlaybackComponent {
+ public final class MediaCodec {
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -21183,7 +21183,6 @@
method @NonNull public android.media.MediaFormat getOutputFormat(int);
method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
method @Nullable public android.media.Image getOutputImage(int);
- method public String getPlaybackId();
method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
@@ -21199,7 +21198,6 @@
method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
method public void setOutputSurface(@NonNull android.view.Surface);
method public void setParameters(@Nullable android.os.Bundle);
- method public void setPlaybackId(@NonNull String);
method public void setVideoScalingMode(int);
method public void signalEndOfInputStream();
method public void start();
@@ -22035,7 +22033,7 @@
ctor public MediaDrmResetException(String);
}
- public final class MediaExtractor {
+ public final class MediaExtractor implements android.media.metrics.PlaybackComponent {
ctor public MediaExtractor();
method public boolean advance();
method protected void finalize();
@@ -22044,6 +22042,7 @@
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
method public android.os.PersistableBundle getMetrics();
+ method @NonNull public String getPlaybackId();
method @Nullable public java.util.Map<java.util.UUID,byte[]> getPsshInfo();
method public boolean getSampleCryptoInfo(@NonNull android.media.MediaCodec.CryptoInfo);
method public int getSampleFlags();
@@ -22065,6 +22064,7 @@
method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
method public void setMediaCas(@NonNull android.media.MediaCas);
+ method public void setPlaybackId(@NonNull String);
method public void unselectTrack(int);
field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
@@ -22198,7 +22198,6 @@
field public static final String KEY_ROTATION = "rotation-degrees";
field public static final String KEY_SAMPLE_RATE = "sample-rate";
field public static final String KEY_SLICE_HEIGHT = "slice-height";
- field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
field public static final String KEY_STRIDE = "stride";
field public static final String KEY_TEMPORAL_LAYERING = "ts-schema";
field public static final String KEY_TILE_HEIGHT = "tile-height";
@@ -42038,6 +42037,7 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public int getDeviceToDeviceStatusSharing(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
method public static int getSlotIndex(int);
method @Nullable public int[] getSubscriptionIds(int);
@@ -42050,6 +42050,7 @@
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharing(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42061,6 +42062,11 @@
field public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
field public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
field public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
+ field public static final int D2D_SHARING_ALL = 3; // 0x3
+ field public static final int D2D_SHARING_ALL_CONTACTS = 1; // 0x1
+ field public static final int D2D_SHARING_DISABLED = 0; // 0x0
+ field public static final int D2D_SHARING_STARRED_CONTACTS = 2; // 0x2
+ field public static final String D2D_STATUS_SHARING = "d2d_sharing_status";
field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bc490af..c964540 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8841,6 +8841,7 @@
field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
+ field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
field public static final String NAMESPACE_SYSTEMUI = "systemui";
field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
field public static final String NAMESPACE_TELEPHONY = "telephony";
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index c812e8e..f7a3514 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -450,6 +450,21 @@
public abstract boolean hasRunningForegroundService(int uid, int foregroundServiceType);
/**
+ * Returns {@code true} if the given notification channel currently has a
+ * notification associated with a foreground service. This is an AMS check
+ * because that is the source of truth for the FGS state.
+ */
+ public abstract boolean hasForegroundServiceNotification(String pkg, @UserIdInt int userId,
+ String channelId);
+
+ /**
+ * If the given app has any FGSs whose notifications are in the given channel,
+ * stop them.
+ */
+ public abstract void stopForegroundServicesForChannel(String pkg, @UserIdInt int userId,
+ String channelId);
+
+ /**
* Registers the specified {@code processObserver} to be notified of future changes to
* process state.
*/
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index 9007d9d..b060ce2 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -53,8 +53,6 @@
-
-
// Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
@@ -242,7 +240,7 @@
};
@DataClass.Generated(
- time = 1612832377589L,
+ time = 1611795283642L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/companion/Association.java",
inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\nprivate final long mTimeApprovedMs\npublic int getUserId()\nprivate java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d352b27..0c7cccf 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1787,7 +1787,7 @@
* the package name of the current installed package to be uninstalled.
* You can optionally supply {@link #EXTRA_RETURN_RESULT}.
* <p>
- * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
+ * Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the uninstall
* succeeded.
* <p>
* Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d79b66c..142b17c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6776,7 +6776,7 @@
@NonNull
public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
Configuration configuration) throws NameNotFoundException {
- throw new UnsupportedOperationException();
+ return getResourcesForApplication(app);
}
/**
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 16d041a..d026e95 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -34,6 +34,9 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
/**
* Java proxy for a native IBinder object.
@@ -262,27 +265,45 @@
Log.e(Binder.TAG, "RemoteException while disabling app freezer");
}
- for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
- BinderProxy bp = weakRef.get();
- String key;
- if (bp == null) {
- key = "<cleared weak-ref>";
- } else {
- try {
- key = bp.getInterfaceDescriptor();
- if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
- key = "<proxy to dead node>";
+ // We run the dump on a separate thread, because there are known cases where
+ // a process overrides getInterfaceDescriptor() and somehow blocks on it, causing
+ // the calling thread (usually AMS) to hit the watchdog.
+ // Do the dumping on a separate thread instead, and give up after a while.
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ executorService.submit(() -> {
+ for (WeakReference<BinderProxy> weakRef : proxiesToQuery) {
+ BinderProxy bp = weakRef.get();
+ String key;
+ if (bp == null) {
+ key = "<cleared weak-ref>";
+ } else {
+ try {
+ key = bp.getInterfaceDescriptor();
+ if ((key == null || key.isEmpty()) && !bp.isBinderAlive()) {
+ key = "<proxy to dead node>";
+ }
+ } catch (Throwable t) {
+ key = "<exception during getDescriptor>";
}
- } catch (Throwable t) {
- key = "<exception during getDescriptor>";
+ }
+ Integer i = counts.get(key);
+ if (i == null) {
+ counts.put(key, 1);
+ } else {
+ counts.put(key, i + 1);
}
}
- Integer i = counts.get(key);
- if (i == null) {
- counts.put(key, 1);
- } else {
- counts.put(key, i + 1);
+ });
+
+ try {
+ executorService.shutdown();
+ boolean dumpDone = executorService.awaitTermination(20, TimeUnit.SECONDS);
+ if (!dumpDone) {
+ Log.e(Binder.TAG, "Failed to complete binder proxy dump,"
+ + " dumping what we have so far.");
}
+ } catch (InterruptedException e) {
+ // Ignore
}
try {
ActivityManager.getService().enableAppFreezer(true);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a5b0e8d..b44c752 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1305,7 +1305,11 @@
public static final boolean IS_USER = "user".equals(TYPE);
/**
- * Whether this build is running inside a container.
+ * Whether this build is running on ARC, the Android Runtime for Chrome
+ * (https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md).
+ * Prior to R this was implemented as a container but from R this will be
+ * a VM. The name of the property remains ro.boot.conntainer as it is
+ * referenced in other projects.
*
* We should try to avoid checking this flag if possible to minimize
* unnecessarily diverging from non-container Android behavior.
@@ -1316,7 +1320,7 @@
* For higher-level behavior differences, other checks should be preferred.
* @hide
*/
- public static final boolean IS_CONTAINER =
+ public static final boolean IS_ARC =
SystemProperties.getBoolean("ro.boot.container", false);
/**
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 1189fdb..fdf8ef0 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -114,20 +114,18 @@
*
* <pre>
* public void onCreate() {
- * if (DEVELOPER_MODE) {
- * StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
- * .detectDiskReads()
- * .detectDiskWrites()
- * .detectNetwork() // or .detectAll() for all detectable problems
- * .penaltyLog()
- * .build());
- * StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
- * .detectLeakedSqlLiteObjects()
- * .detectLeakedClosableObjects()
- * .penaltyLog()
- * .penaltyDeath()
- * .build());
- * }
+ * StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
+ * .detectDiskReads()
+ * .detectDiskWrites()
+ * .detectNetwork() // or .detectAll() for all detectable problems
+ * .penaltyLog()
+ * .build());
+ * StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
+ * .detectLeakedSqlLiteObjects()
+ * .detectLeakedClosableObjects()
+ * .penaltyLog()
+ * .penaltyDeath()
+ * .build());
* super.onCreate();
* }
* </pre>
@@ -146,9 +144,7 @@
* <p class="note">StrictMode is not a security mechanism and is not guaranteed to find all disk or
* network accesses. While it does propagate its state across process boundaries when doing {@link
* android.os.Binder} calls, it's still ultimately a best effort mechanism. Notably, disk or network
- * access from JNI calls won't necessarily trigger it. Future versions of Android may catch more (or
- * fewer) operations, so you should never leave StrictMode enabled in applications distributed on
- * Google Play.
+ * access from JNI calls won't necessarily trigger it.
*/
public final class StrictMode {
private static final String TAG = "StrictMode";
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 98b4e0b..5fe870f 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -198,7 +198,7 @@
void clearUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 88;
void fixupAppDir(in String path) = 89;
void disableAppDataIsolation(in String pkgName, int pid, int userId) = 90;
- void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91;
- void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
- PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
- }
+ PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 91;
+ void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 92;
+ void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 93;
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index e9bbcc79..12964b5 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -376,6 +376,15 @@
public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
/**
+ * Namespace for swcodec native related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
+
+
+ /**
* Namespace for System UI related features.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 85cef84..7a57ce0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8498,6 +8498,15 @@
"one_handed_tutorial_show_count";
/**
+ * Indicates whether transform is enabled.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRANSFORM_ENABLED = "transform_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 8a4812a..3da1075 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5304,6 +5304,13 @@
public static final String COLUMN_RCS_CONFIG = "rcs_config";
/**
+ * TelephonyProvider column name for device to device sharing status.
+ *
+ * @hide
+ */
+ public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
+
+ /**
* TelephonyProvider column name for VoIMS provisioning. Default is 0.
* <P>Type: INTEGER </P>
*
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index cbb86de..a86984f 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -78,3 +78,7 @@
per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+
+# Scroll Capture
+per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6cfd498..00bcdda 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2165,10 +2165,10 @@
+ " resultList.size()=" + resultList.size()
+ " appTargets.size()=" + appTargets.size());
}
-
+ Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
for (int i = resultList.size() - 1; i >= 0; i--) {
final String packageName = resultList.get(i).getTargetComponent().getPackageName();
- if (!isPackageEnabled(packageName)) {
+ if (!isPackageEnabled(selectedProfileContext, packageName)) {
resultList.remove(i);
if (appTargets != null) {
appTargets.remove(i);
@@ -2220,13 +2220,13 @@
mChooserHandler.sendMessage(msg);
}
- private boolean isPackageEnabled(String packageName) {
+ private boolean isPackageEnabled(Context context, String packageName) {
if (TextUtils.isEmpty(packageName)) {
return false;
}
ApplicationInfo appInfo;
try {
- appInfo = getPackageManager().getApplicationInfo(packageName, 0);
+ appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
} catch (NameNotFoundException e) {
return false;
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index ee98878..236f8507 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -415,6 +415,12 @@
public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
/**
+ * (boolean) Whether dark launch of remote prediction service is enabled.
+ */
+ public static final String DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED =
+ "dark_launch_remote_prediction_service_enabled";
+
+ /**
* (boolean) Whether to enable pinch resizing for PIP.
*/
public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index b3e8db2..b39dc66 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -57,6 +57,7 @@
public static final boolean DEFAULT_TRACK_SCREEN_INTERACTIVE = false;
public static final boolean DEFAULT_TRACK_DIRECT_CALLING_UID = true;
public static final boolean DEFAULT_IGNORE_BATTERY_STATUS = false;
+ public static final boolean DEFAULT_COLLECT_LATENCY_DATA = false;
public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 1500;
private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
@@ -97,11 +98,13 @@
private boolean mTrackDirectCallingUid = DEFAULT_TRACK_DIRECT_CALLING_UID;
private boolean mTrackScreenInteractive = DEFAULT_TRACK_SCREEN_INTERACTIVE;
private boolean mIgnoreBatteryStatus = DEFAULT_IGNORE_BATTERY_STATUS;
+ private boolean mCollectLatencyData = DEFAULT_COLLECT_LATENCY_DATA;
private CachedDeviceState.Readonly mDeviceState;
private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
private static final int CALL_STATS_OBSERVER_DEBOUNCE_MILLIS = 5000;
+ private BinderLatencyObserver mLatencyObserver;
private BinderInternal.CallStatsObserver mCallStatsObserver;
private ArraySet<Integer> mSendUidsToObserver = new ArraySet<>(32);
private final Handler mCallStatsObserverHandler;
@@ -148,11 +151,16 @@
public Handler getHandler() {
return new Handler(Looper.getMainLooper());
}
+
+ public BinderLatencyObserver getLatencyObserver() {
+ return new BinderLatencyObserver(new BinderLatencyObserver.Injector());
+ }
}
public BinderCallsStats(Injector injector) {
this.mRandom = injector.getRandomGenerator();
this.mCallStatsObserverHandler = injector.getHandler();
+ this.mLatencyObserver = injector.getLatencyObserver();
}
public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
@@ -202,7 +210,10 @@
if (mRecordingAllTransactionsForUid || s.recordedCall) {
s.cpuTimeStarted = getThreadTimeMicro();
s.timeStarted = getElapsedRealtimeMicro();
+ } else if (mCollectLatencyData) {
+ s.timeStarted = getElapsedRealtimeMicro();
}
+
return s;
}
@@ -227,6 +238,10 @@
private void processCallEnded(CallSession s,
int parcelRequestSize, int parcelReplySize, int workSourceUid) {
+ if (mCollectLatencyData) {
+ mLatencyObserver.callEnded(s);
+ }
+
UidEntry uidEntry = null;
final boolean recordCall;
if (s.recordedCall) {
@@ -752,6 +767,17 @@
}
}
+ /** Whether to collect latency histograms. */
+ public void setCollectLatencyData(boolean collectLatencyData) {
+ mCollectLatencyData = collectLatencyData;
+ }
+
+ /** Whether to collect latency histograms. */
+ @VisibleForTesting
+ public boolean getCollectLatencyData() {
+ return mCollectLatencyData;
+ }
+
public void reset() {
synchronized (mLock) {
mCallStatsCount = 0;
@@ -763,6 +789,8 @@
mBatteryStopwatch.reset();
}
mRecordingAllTransactionsForUid = false;
+ // Do not reset the latency observer as binder stats and latency will be pushed to WW
+ // at different intervals so the resets should not be coupled.
}
}
@@ -987,6 +1015,10 @@
return mExceptionCounts;
}
+ public BinderLatencyObserver getLatencyObserver() {
+ return mLatencyObserver;
+ }
+
@VisibleForTesting
public static <T> List<T> getHighestValues(List<T> list, ToDoubleFunction<T> toDouble,
double percentile) {
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
new file mode 100644
index 0000000..92b4952
--- /dev/null
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BinderInternal.CallSession;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/** Collects statistics about Binder call latency per calling API and method. */
+public class BinderLatencyObserver {
+ private static final String TAG = "BinderLatencyObserver";
+ public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+
+ // This is not the final data structure - we will eventually store latency histograms instead of
+ // raw samples as it is much more memory / disk space efficient.
+ // TODO(b/179999191): change this to store the histogram.
+ // TODO(b/179999191): pre allocate the array size so we would not have to resize this.
+ @GuardedBy("mLock")
+ private final ArrayMap<LatencyDims, ArrayList<Long>> mLatencySamples = new ArrayMap<>();
+ private final Object mLock = new Object();
+
+ // Sampling period to control how often to track CPU usage. 1 means all calls, 100 means ~1 out
+ // of 100 requests.
+ private int mPeriodicSamplingInterval = PERIODIC_SAMPLING_INTERVAL_DEFAULT;
+ private final Random mRandom;
+
+ /** Injector for {@link BinderLatencyObserver}. */
+ public static class Injector {
+ public Random getRandomGenerator() {
+ return new Random();
+ }
+ }
+
+ public BinderLatencyObserver(Injector injector) {
+ mRandom = injector.getRandomGenerator();
+ }
+
+ /** Should be called when a Binder call completes, will store latency data. */
+ public void callEnded(@Nullable CallSession s) {
+ if (s == null || s.exceptionThrown || !shouldKeepSample()) {
+ return;
+ }
+
+ LatencyDims dims = new LatencyDims(s.binderClass, s.transactionCode);
+ long callDuration = getElapsedRealtimeMicro() - s.timeStarted;
+
+ synchronized (mLock) {
+ if (!mLatencySamples.containsKey(dims)) {
+ mLatencySamples.put(dims, new ArrayList<Long>());
+ }
+
+ mLatencySamples.get(dims).add(callDuration);
+ }
+ }
+
+ protected long getElapsedRealtimeMicro() {
+ return SystemClock.elapsedRealtimeNanos() / 1000;
+ }
+
+ protected boolean shouldKeepSample() {
+ return mRandom.nextInt() % mPeriodicSamplingInterval == 0;
+ }
+
+ /** Updates the sampling interval. */
+ public void setSamplingInterval(int samplingInterval) {
+ if (samplingInterval <= 0) {
+ Slog.w(TAG, "Ignored invalid sampling interval (value must be positive): "
+ + samplingInterval);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (samplingInterval != mPeriodicSamplingInterval) {
+ mPeriodicSamplingInterval = samplingInterval;
+ reset();
+ }
+ }
+ }
+
+ /** Resets the sample collection. */
+ public void reset() {
+ synchronized (mLock) {
+ mLatencySamples.clear();
+ }
+ }
+
+ /** Container for binder latency information. */
+ public static class LatencyDims {
+ // Binder interface descriptor.
+ private Class<? extends Binder> mBinderClass;
+ // Binder transaction code.
+ private int mTransactionCode;
+ // Cached hash code, 0 if not set yet.
+ private int mHashCode = 0;
+
+ public LatencyDims(Class<? extends Binder> binderClass, int transactionCode) {
+ this.mBinderClass = binderClass;
+ this.mTransactionCode = transactionCode;
+ }
+
+ public Class<? extends Binder> getBinderClass() {
+ return mBinderClass;
+ }
+
+ public int getTransactionCode() {
+ return mTransactionCode;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || !(other instanceof LatencyDims)) {
+ return false;
+ }
+ LatencyDims o = (LatencyDims) other;
+ return mTransactionCode == o.getTransactionCode() && mBinderClass == o.getBinderClass();
+ }
+
+ @Override
+ public int hashCode() {
+ if (mHashCode != 0) {
+ return mHashCode;
+ }
+ int hash = mTransactionCode;
+ hash = 31 * hash + mBinderClass.hashCode();
+ mHashCode = hash;
+ return hash;
+ }
+ }
+
+ @VisibleForTesting
+ public ArrayMap<LatencyDims, ArrayList<Long>> getLatencySamples() {
+ return mLatencySamples;
+ }
+}
diff --git a/core/java/com/android/internal/view/OWNERS b/core/java/com/android/internal/view/OWNERS
index 851d1f3..eb2478f 100644
--- a/core/java/com/android/internal/view/OWNERS
+++ b/core/java/com/android/internal/view/OWNERS
@@ -18,3 +18,7 @@
per-file BaseIWIndow.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file RotationPolicy.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file WindowManagerPolicyThread.java = file:/services/core/java/com/android/server/wm/OWNERS
+
+# Scroll Capture
+per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index f6629fd..c0c4b70 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -42,6 +42,9 @@
per-file android_os_HwRemoteBinder* = file:platform/system/libhwbinder:/OWNERS
per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS
+# Sensor
+per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com
+
per-file *Zygote* = file:/ZYGOTE_OWNERS
per-file fd_utils.* = file:/ZYGOTE_OWNERS
per-file Android.bp = file:platform/build/soong:/OWNERS
diff --git a/core/res/res/values-es-rMX/donottranslate-cldr.xml b/core/res/res/values-es-rMX/donottranslate-cldr.xml
new file mode 100755
index 0000000..db438f2
--- /dev/null
+++ b/core/res/res/values-es-rMX/donottranslate-cldr.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="month_day_year">%-e %B %Y</string>
+ <string name="time_of_day">%H:%M:%S</string>
+ <string name="date_and_time">%-e %b %Y, %H:%M:%S</string>
+ <string name="date_time">%1$s, %2$s</string>
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9bc92e1..2c6a2a1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3177,6 +3177,26 @@
</public-group>
<!-- ===============================================================
+ Resources added in version T of the platform
+
+ NOTE: add <public> elements within a <public-group> like so:
+
+ <public-group type="attr" first-id="0x01010531">
+ <public name="exampleAttr1" />
+ <public name="exampleAttr2" />
+ </public-group>
+
+ To add a new public-group block, choose an id value that is 1 greater
+ than the last of that item above. For example, the last "attr" id
+ value above is 0x01010530, so the public-group of attrs below has
+ the id value of 0x01010531.
+ =============================================================== -->
+ <eat-comment />
+
+ <public-group type="attr" first-id="0x01010640">
+ </public-group>
+
+ <!-- ===============================================================
DO NOT ADD UN-GROUPED ITEMS HERE
Any new items (attrs, styles, ids, etc.) *must* be added in a
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 80165f0..fa1aa5e 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -9,3 +9,6 @@
per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS
+
+# Scroll Capture
+per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 561c549..5334a45 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -923,6 +923,34 @@
}
}
+ @Test
+ public void testLatencyCollectionEnabled() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setCollectLatencyData(true);
+
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+ bcs.time += 10;
+ bcs.elapsedTime += 20;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+ assertEquals(1, bcs.getLatencyObserver().getLatencySamples().size());
+ }
+
+ @Test
+ public void testLatencyCollectionDisabledByDefault() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ assertEquals(false, bcs.getCollectLatencyData());
+
+ Binder binder = new Binder();
+ CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
+ bcs.time += 10;
+ bcs.elapsedTime += 20;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+
+ assertEquals(0, bcs.getLatencyObserver().getLatencySamples().size());
+ }
+
private static class TestHandler extends Handler {
ArrayList<Runnable> mRunnables = new ArrayList<>();
@@ -963,6 +991,10 @@
public Handler getHandler() {
return mHandler;
}
+
+ public BinderLatencyObserver getLatencyObserver() {
+ return new BinderLatencyObserverTest.TestBinderLatencyObserver();
+ }
});
setSamplingInterval(1);
setAddDebugEntries(false);
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
new file mode 100644
index 0000000..36915a2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.BinderLatencyObserver.LatencyDims;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class BinderLatencyObserverTest {
+ @Test
+ public void testLatencyCollectionWithMultipleClasses() {
+ TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+ Binder binder = new Binder();
+ CallSession callSession = new CallSession();
+ callSession.binderClass = binder.getClass();
+ callSession.transactionCode = 1;
+ blo.callEnded(callSession);
+ blo.callEnded(callSession);
+ callSession.transactionCode = 2;
+ blo.callEnded(callSession);
+
+ ArrayMap<LatencyDims, ArrayList<Long>> latencySamples = blo.getLatencySamples();
+ assertEquals(2, latencySamples.keySet().size());
+ assertThat(latencySamples.get(new LatencyDims(binder.getClass(), 1)))
+ .containsExactlyElementsIn(Arrays.asList(1L, 2L));
+ assertThat(latencySamples.get(new LatencyDims(binder.getClass(), 2))).containsExactly(3L);
+ }
+
+ @Test
+ public void testSampling() {
+ TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+ blo.setSamplingInterval(2);
+
+ Binder binder = new Binder();
+ CallSession callSession = new CallSession();
+ callSession.binderClass = binder.getClass();
+ callSession.transactionCode = 1;
+ blo.callEnded(callSession);
+ callSession.transactionCode = 2;
+ blo.callEnded(callSession);
+
+ ArrayMap<LatencyDims, ArrayList<Long>> latencySamples = blo.getLatencySamples();
+ assertEquals(1, latencySamples.size());
+ LatencyDims dims = latencySamples.keySet().iterator().next();
+ assertEquals(binder.getClass(), dims.getBinderClass());
+ assertEquals(1, dims.getTransactionCode());
+ ArrayList<Long> values = latencySamples.get(dims);
+ assertThat(values).containsExactly(1L);
+ }
+
+ public static class TestBinderLatencyObserver extends BinderLatencyObserver {
+ private long mElapsedTimeCallCount = 0;
+
+ TestBinderLatencyObserver() {
+ // Make random generator not random.
+ super(new Injector() {
+ public Random getRandomGenerator() {
+ return new Random() {
+ int mCallCount = 0;
+
+ public int nextInt() {
+ return mCallCount++;
+ }
+ };
+ }
+ });
+ setSamplingInterval(1);
+ }
+
+ @Override
+ protected long getElapsedRealtimeMicro() {
+ return ++mElapsedTimeCallCount;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/view/OWNERS b/core/tests/coretests/src/com/android/internal/view/OWNERS
new file mode 100644
index 0000000..1dad10d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/view/OWNERS
@@ -0,0 +1,3 @@
+# Scroll Capture
+per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/data/etc/car/com.android.car.activityresolver.xml b/data/etc/car/com.android.car.activityresolver.xml
index d48bc15..927c738 100644
--- a/data/etc/car/com.android.car.activityresolver.xml
+++ b/data/etc/car/com.android.car.activityresolver.xml
@@ -19,3 +19,4 @@
<permission name="android.permission.MANAGE_USERS"/>
</privapp-permissions>
</permissions>
+
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fae89d6..6056af0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -369,6 +369,8 @@
<!-- Needed for test only -->
<permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
<permission name="android.permission.POWER_SAVER" />
+ <!-- Needed for CTS tests -->
+ <permission name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
<permission name="android.permission.READ_CARRIER_APP_INFO"/>
<permission name="android.permission.READ_FRAME_BUFFER"/>
<permission name="android.permission.READ_LOWPAN_CREDENTIAL"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index a1b1de3..ab42e77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -272,7 +272,6 @@
final int yOffSet = Math.round(getDisplaySize().y * mOffSetFraction);
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
mTimeoutHandler.resetTimer();
-
mOneHandedUiEventLogger.writeEvent(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
}
@@ -283,6 +282,9 @@
if (mDisplayAreaOrganizer.isInOneHanded()) {
mDisplayAreaOrganizer.scheduleOffset(0, 0);
mTimeoutHandler.removeTimer();
+ // Log metrics for Gesture navigation mode.
+ mOneHandedUiEventLogger.writeEvent(
+ OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
}
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d663c52..f0cba44 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -648,6 +648,7 @@
"tests/unit/CommonPoolTests.cpp",
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
+ "tests/unit/EglManagerTests.cpp",
"tests/unit/FatVectorTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 3aa5b4b..ca5f853 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -17,8 +17,10 @@
#pragma once
#include "pipeline/skia/SkiaDisplayList.h"
+#include "canvas/CanvasOpBuffer.h"
#include <memory>
+#include <variant>
namespace android {
namespace uirenderer {
@@ -28,29 +30,25 @@
};
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
-/**
- * Data structure that holds the list of commands used in display list stream
- */
-//using DisplayList = skiapipeline::SkiaDisplayList;
-class DisplayList {
+class SkiaDisplayListWrapper {
public:
// Constructs an empty (invalid) DisplayList
- explicit DisplayList() {}
+ explicit SkiaDisplayListWrapper() {}
// Constructs a DisplayList from a SkiaDisplayList
- explicit DisplayList(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
+ explicit SkiaDisplayListWrapper(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
: mImpl(std::move(impl)) {}
// Move support
- DisplayList(DisplayList&& other) : mImpl(std::move(other.mImpl)) {}
- DisplayList& operator=(DisplayList&& other) {
+ SkiaDisplayListWrapper(SkiaDisplayListWrapper&& other) : mImpl(std::move(other.mImpl)) {}
+ SkiaDisplayListWrapper& operator=(SkiaDisplayListWrapper&& other) {
mImpl = std::move(other.mImpl);
return *this;
}
// No copy support
- DisplayList(const DisplayList& other) = delete;
- DisplayList& operator=(const DisplayList&) = delete;
+ SkiaDisplayListWrapper(const SkiaDisplayListWrapper& other) = delete;
+ SkiaDisplayListWrapper& operator=(const SkiaDisplayListWrapper&) = delete;
void updateChildren(std::function<void(RenderNode*)> updateFn) {
mImpl->updateChildren(std::move(updateFn));
@@ -137,7 +135,7 @@
void applyColorTransform(ColorTransform transform) {
if (mImpl) {
- mImpl->mDisplayList.applyColorTransform(transform);
+ mImpl->applyColorTransform(transform);
}
}
@@ -145,5 +143,172 @@
std::unique_ptr<skiapipeline::SkiaDisplayList> mImpl;
};
+
+/**
+ * Data structure that holds the list of commands used in display list stream
+ */
+//using DisplayList = skiapipeline::SkiaDisplayList;
+class MultiDisplayList {
+private:
+ using SkiaDisplayList = skiapipeline::SkiaDisplayList;
+
+ struct EmptyList {
+ bool hasText() const { return false; }
+ void updateChildren(std::function<void(RenderNode*)> updateFn) {}
+ bool isEmpty() const { return true; }
+ bool containsProjectionReceiver() const { return false; }
+ bool hasVectorDrawables() const { return false; }
+ size_t getUsedSize() const { return 0; }
+ size_t getAllocatedSize() const { return 0; }
+ void output(std::ostream& output, uint32_t level) const { }
+ bool hasFunctor() const { return false; }
+ bool prepareListAndChildren(
+ TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
+ return false;
+ }
+ void syncContents(const WebViewSyncData& data) { }
+ void applyColorTransform(ColorTransform transform) { }
+ };
+
+ std::variant<EmptyList, std::unique_ptr<SkiaDisplayList>, CanvasOpBuffer> mImpls;
+
+ template <typename T>
+ static constexpr T& get(T& t) { return t; }
+ template <typename T>
+ static constexpr const T& get(const T& t) { return t; }
+
+ template <typename T>
+ static constexpr T& get(std::unique_ptr<T>& t) { return *t; }
+ template <typename T>
+ static constexpr const T& get(const std::unique_ptr<T>& t) { return *t; }
+
+ template <typename T>
+ auto apply(T&& t) {
+ return std::visit([&t](auto& it) -> auto {
+ return t(get(it));
+ }, mImpls);
+ }
+
+ template <typename T>
+ auto apply(T&& t) const {
+ return std::visit([&t](const auto& it) -> auto {
+ return t(get(it));
+ }, mImpls);
+ }
+
+public:
+ // Constructs an empty (invalid) DisplayList
+ explicit MultiDisplayList() {}
+
+ // Constructs a DisplayList from a SkiaDisplayList
+ explicit MultiDisplayList(std::unique_ptr<SkiaDisplayList> impl)
+ : mImpls(std::move(impl)) {}
+
+ explicit MultiDisplayList(CanvasOpBuffer&& opBuffer) : mImpls(std::move(opBuffer)) {}
+
+ // Move support
+ MultiDisplayList(MultiDisplayList&& other) : mImpls(std::move(other.mImpls)) {}
+ MultiDisplayList& operator=(MultiDisplayList&& other) {
+ mImpls = std::move(other.mImpls);
+ return *this;
+ }
+
+ // No copy support
+ MultiDisplayList(const MultiDisplayList& other) = delete;
+ MultiDisplayList& operator=(const MultiDisplayList&) = delete;
+
+ void updateChildren(std::function<void(RenderNode*)> updateFn) {
+ apply([&](auto& it) { it.updateChildren(std::move(updateFn)); });
+ }
+
+ [[nodiscard]] explicit operator bool() const {
+ return isValid();
+ }
+
+ // If true this DisplayList contains a backing content, even if that content is empty
+ // If false, there this DisplayList is in an "empty" state
+ [[nodiscard]] bool isValid() const {
+ return mImpls.index() != 0;
+ }
+
+ [[nodiscard]] bool isEmpty() const {
+ return apply([](const auto& it) -> auto { return it.isEmpty(); });
+ }
+
+ [[nodiscard]] bool hasContent() const {
+ return !isEmpty();
+ }
+
+ [[nodiscard]] bool containsProjectionReceiver() const {
+ return apply([](const auto& it) -> auto { return it.containsProjectionReceiver(); });
+ }
+
+ [[nodiscard]] SkiaDisplayList* asSkiaDl() {
+ return std::get<1>(mImpls).get();
+ }
+
+ [[nodiscard]] const SkiaDisplayList* asSkiaDl() const {
+ return std::get<1>(mImpls).get();
+ }
+
+ [[nodiscard]] bool hasVectorDrawables() const {
+ return apply([](const auto& it) -> auto { return it.hasVectorDrawables(); });
+ }
+
+ void clear(RenderNode* owningNode = nullptr) {
+ if (owningNode && mImpls.index() == 1) {
+ auto& skiaDl = std::get<1>(mImpls);
+ if (skiaDl->reuseDisplayList(owningNode)) {
+ skiaDl.release();
+ }
+ }
+ mImpls = EmptyList{};
+ }
+
+ [[nodiscard]] size_t getUsedSize() const {
+ return apply([](const auto& it) -> auto { return it.getUsedSize(); });
+ }
+
+ [[nodiscard]] size_t getAllocatedSize() const {
+ return apply([](const auto& it) -> auto { return it.getAllocatedSize(); });
+ }
+
+ void output(std::ostream& output, uint32_t level) const {
+ apply([&](const auto& it) { it.output(output, level); });
+ }
+
+ [[nodiscard]] bool hasFunctor() const {
+ return apply([](const auto& it) -> auto { return it.hasFunctor(); });
+ }
+
+ bool prepareListAndChildren(
+ TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
+ return apply([&](auto& it) -> auto {
+ return it.prepareListAndChildren(observer, info, functorsNeedLayer, std::move(childFn));
+ });
+ }
+
+ void syncContents(const WebViewSyncData& data) {
+ apply([&](auto& it) { it.syncContents(data); });
+ }
+
+ [[nodiscard]] bool hasText() const {
+ return apply([](const auto& it) -> auto { return it.hasText(); });
+ }
+
+ void applyColorTransform(ColorTransform transform) {
+ apply([=](auto& it) { it.applyColorTransform(transform); });
+ }
+
+ [[nodiscard]] CanvasOpBuffer& asOpBuffer() {
+ return std::get<CanvasOpBuffer>(mImpls);
+ }
+};
+
+// For now stick to the original single-type container to avoid any regressions
+using DisplayList = SkiaDisplayListWrapper;
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
index d749d2f..f9a6101 100644
--- a/libs/hwui/canvas/CanvasFrontend.h
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -147,8 +147,7 @@
public:
template<class... Args>
CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height),
- mReceiver(std::forward<Args>(args)...) { }
- ~CanvasFrontend() = default;
+ mReceiver(std::in_place, std::forward<Args>(args)...) { }
void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) {
if (internalSave(flagsToSaveEntry(flags))) {
@@ -186,7 +185,10 @@
submit(std::move(op));
}
- const CanvasOpReceiver& receiver() const { return *mReceiver; }
+ const CanvasOpReceiver& receiver() const {
+ LOG_ALWAYS_FATAL_IF(!mReceiver.has_value());
+ return *mReceiver;
+ }
CanvasOpReceiver finish() {
auto ret = std::move(mReceiver.value());
@@ -205,6 +207,7 @@
template <CanvasOpType T>
void submit(CanvasOp<T>&& op) {
+ LOG_ALWAYS_FATAL_IF(!mReceiver.has_value());
mReceiver->push_container(CanvasOpContainer(std::move(op), transform()));
}
};
diff --git a/libs/hwui/canvas/CanvasOpBuffer.cpp b/libs/hwui/canvas/CanvasOpBuffer.cpp
index 7054e47e..6089c572 100644
--- a/libs/hwui/canvas/CanvasOpBuffer.cpp
+++ b/libs/hwui/canvas/CanvasOpBuffer.cpp
@@ -22,4 +22,32 @@
template class OpBuffer<CanvasOpType, CanvasOpContainer>;
+void CanvasOpBuffer::updateChildren(std::function<void(RenderNode*)> updateFn) {
+ // TODO: Do we need a fast-path for finding children?
+ if (mHas.children) {
+ for (auto& iter : filter<CanvasOpType::DrawRenderNode>()) {
+ updateFn(iter->renderNode.get());
+ }
+ }
+}
+
+void CanvasOpBuffer::output(std::ostream& output, uint32_t level) const {
+ LOG_ALWAYS_FATAL("TODO");
+}
+
+bool CanvasOpBuffer::prepareListAndChildren(
+ TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
+ LOG_ALWAYS_FATAL("TODO");
+ return false;
+}
+
+void CanvasOpBuffer::syncContents(const WebViewSyncData& data) {
+ LOG_ALWAYS_FATAL("TODO");
+}
+
+void CanvasOpBuffer::applyColorTransform(ColorTransform transform) {
+ LOG_ALWAYS_FATAL("TODO");
+}
+
} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpBuffer.h b/libs/hwui/canvas/CanvasOpBuffer.h
index 07e079a..af797ca 100644
--- a/libs/hwui/canvas/CanvasOpBuffer.h
+++ b/libs/hwui/canvas/CanvasOpBuffer.h
@@ -19,10 +19,17 @@
#include <SkMatrix.h>
#include "CanvasOpTypes.h"
+#include "CanvasTransform.h"
#include "OpBuffer.h"
+#include "TreeInfo.h"
+#include "private/hwui/WebViewFunctor.h"
+
+#include <functional>
namespace android::uirenderer {
+class RenderNode;
+
template <CanvasOpType T>
struct CanvasOp;
@@ -53,12 +60,74 @@
};
extern template class OpBuffer<CanvasOpType, CanvasOpContainer>;
-class CanvasOpBuffer final : public OpBuffer<CanvasOpType, CanvasOpContainer> {
+class CanvasOpBuffer final : private OpBuffer<CanvasOpType, CanvasOpContainer> {
+private:
+ using SUPER = OpBuffer<CanvasOpType, CanvasOpContainer>;
+
public:
+ // Expose select superclass methods publicly
+ using SUPER::for_each;
+ using SUPER::size;
+ using SUPER::resize;
+
template <CanvasOpType T>
void push(CanvasOp<T>&& op) {
push_container(CanvasOpContainer<T>(std::move(op)));
}
+
+ template <CanvasOpType T>
+ void push_container(CanvasOpContainer<T>&& op) {
+ if constexpr (IsDrawOp(T)) {
+ mHas.content = true;
+ }
+ if constexpr (T == CanvasOpType::DrawRenderNode) {
+ mHas.children = true;
+ // use staging property, since recording on UI thread
+ if (op->renderNode->stagingProperties().isProjectionReceiver()) {
+ mHas.projectionReceiver = true;
+ }
+ }
+ SUPER::push_container(std::move(op));
+ }
+
+ void clear() {
+ mHas = Contains{};
+ SUPER::clear();
+ }
+
+ void updateChildren(std::function<void(RenderNode*)> updateFn);
+ bool prepareListAndChildren(
+ TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+ std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn);
+ void syncContents(const WebViewSyncData& data);
+ void applyColorTransform(ColorTransform transform);
+
+ [[nodiscard]] bool isEmpty() const { return !mHas.content; }
+ [[nodiscard]] bool hasText() const { return mHas.text; }
+ [[nodiscard]] bool hasVectorDrawables() const { return mHas.vectorDrawable; }
+ [[nodiscard]] bool containsProjectionReceiver() const { return mHas.projectionReceiver; }
+ [[nodiscard]] bool hasFunctor() const { return mHas.functor; }
+
+ [[nodiscard]] size_t getUsedSize() const {
+ return size();
+ }
+
+ [[nodiscard]] size_t getAllocatedSize() const {
+ return capacity();
+ }
+
+ void output(std::ostream& output, uint32_t level) const;
+
+private:
+ struct Contains {
+ bool content : 1 = false;
+ bool children : 1 = false;
+ bool projectionReceiver : 1 = false;
+ bool text : 1 = false;
+ bool vectorDrawable : 1 = false;
+ bool functor : 1 = false;
+ };
+ Contains mHas;
};
} // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasOpRasterizer.cpp b/libs/hwui/canvas/CanvasOpRasterizer.cpp
index 0093c38..9297604 100644
--- a/libs/hwui/canvas/CanvasOpRasterizer.cpp
+++ b/libs/hwui/canvas/CanvasOpRasterizer.cpp
@@ -33,21 +33,15 @@
SkMatrix& currentGlobalTransform = globalMatrixStack.emplace_back(SkMatrix::I());
source.for_each([&]<CanvasOpType T>(const CanvasOpContainer<T> * op) {
- if constexpr (
- T == CanvasOpType::BeginZ ||
- T == CanvasOpType::EndZ ||
- T == CanvasOpType::DrawLayer
- ) {
- // Do beginZ or endZ
- LOG_ALWAYS_FATAL("TODO");
- return;
- } else {
+ if constexpr (CanvasOpTraits::can_draw<CanvasOp<T>>) {
// Generic OP
// First apply the current transformation
destination->setMatrix(SkMatrix::Concat(currentGlobalTransform, op->transform()));
// Now draw it
(*op)->draw(destination);
+ return;
}
+ LOG_ALWAYS_FATAL("TODO, unable to rasterize %d", static_cast<int>(T));
});
}
diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h
index cde50bd..b55ef9d 100644
--- a/libs/hwui/canvas/CanvasOpTypes.h
+++ b/libs/hwui/canvas/CanvasOpTypes.h
@@ -35,7 +35,8 @@
ClipPath,
// Drawing ops
- DrawColor,
+ DRAW_OP_BEGIN,
+ DrawColor = DRAW_OP_BEGIN,
DrawRect,
DrawRegion,
DrawRoundRect,
@@ -59,10 +60,16 @@
DrawImageLattice,
DrawPicture,
DrawLayer,
+ DrawRenderNode,
+ DRAW_OP_END = DrawRenderNode,
// TODO: Rest
COUNT // must be last
};
+static constexpr bool IsDrawOp(CanvasOpType t) {
+ return CanvasOpType::DRAW_OP_BEGIN <= t && t <= CanvasOpType::DRAW_OP_END;
+}
+
} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index 86b1ac7..855cd0d 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -24,13 +24,15 @@
#include <SkImage.h>
#include <SkPicture.h>
#include <SkRuntimeEffect.h>
-#include <hwui/Bitmap.h>
-#include <log/log.h>
-#include "CanvasProperty.h"
-#include "Points.h"
+#include <log/log.h>
+
+#include "hwui/Bitmap.h"
+#include "CanvasProperty.h"
#include "CanvasOpTypes.h"
#include "Layer.h"
+#include "Points.h"
+#include "RenderNode.h"
#include <experimental/type_traits>
#include <utility>
@@ -450,6 +452,11 @@
sp<Layer> layer;
};
+template<>
+struct CanvasOp<CanvasOpType::DrawRenderNode> {
+ sp<RenderNode> renderNode;
+};
+
// cleanup our macros
#undef ASSERT_DRAWABLE
diff --git a/libs/hwui/canvas/OpBuffer.h b/libs/hwui/canvas/OpBuffer.h
index 1237d69..8b5cdbb 100644
--- a/libs/hwui/canvas/OpBuffer.h
+++ b/libs/hwui/canvas/OpBuffer.h
@@ -64,7 +64,7 @@
static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
using ItemHeader = OpBufferItemHeader<ItemTypes>;
- OpBuffer() = default;
+ explicit OpBuffer() = default;
// Prevent copying by default
OpBuffer(const OpBuffer&) = delete;
@@ -135,7 +135,7 @@
template <typename F>
void for_each(F&& f) const {
- for_each(std::forward<F>(f), ItemTypesSequence{});
+ do_for_each(std::forward<F>(f), ItemTypesSequence{});
}
void clear();
@@ -225,7 +225,7 @@
}
template <typename F, std::size_t... I>
- void for_each(F&& f, std::index_sequence<I...>) const {
+ void do_for_each(F&& f, std::index_sequence<I...>) const {
// Validate we're not empty
if (isEmpty()) return;
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 483264f..1136e58 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -16,6 +16,8 @@
#pragma once
+#include <deque>
+
#include "RecordingCanvas.h"
#include "RenderNodeDrawable.h"
#include "TreeInfo.h"
@@ -23,8 +25,6 @@
#include "utils/LinearAllocator.h"
#include "utils/Pair.h"
-#include <deque>
-
namespace android {
namespace uirenderer {
@@ -46,8 +46,10 @@
class SkiaDisplayList {
public:
- size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); }
- size_t getAllocatedSize() { return allocator.allocatedSize() + mDisplayList.allocatedSize(); }
+ size_t getUsedSize() const { return allocator.usedSize() + mDisplayList.usedSize(); }
+ size_t getAllocatedSize() const {
+ return allocator.allocatedSize() + mDisplayList.allocatedSize();
+ }
~SkiaDisplayList() {
/* Given that we are using a LinearStdAllocator to store some of the
@@ -109,6 +111,10 @@
*/
void syncContents(const WebViewSyncData& data);
+ void applyColorTransform(ColorTransform transform) {
+ mDisplayList.applyColorTransform(transform);
+ }
+
/**
* ONLY to be called by RenderNode::prepareTree in order to prepare this
* list while the UI thread is blocked. Here we can upload mutable bitmaps
@@ -154,12 +160,12 @@
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
+
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
+
public:
- void appendVD(VectorDrawableRoot* r) {
- appendVD(r, SkMatrix::I());
- }
+ void appendVD(VectorDrawableRoot* r) { appendVD(r, SkMatrix::I()); }
void appendVD(VectorDrawableRoot* r, const SkMatrix& mat) {
mVectorDrawables.push_back(Pair<VectorDrawableRoot*, SkMatrix>(r, mat));
diff --git a/libs/hwui/tests/unit/CanvasFrontendTests.cpp b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
index 05b1179..4ddcf6f 100644
--- a/libs/hwui/tests/unit/CanvasFrontendTests.cpp
+++ b/libs/hwui/tests/unit/CanvasFrontendTests.cpp
@@ -124,12 +124,12 @@
TEST(CanvasFrontend, drawOpTransform) {
CanvasFrontend<CanvasOpBuffer> opCanvas(100, 100);
- const auto& receiver = opCanvas.receiver();
+ const auto &receiver = opCanvas.receiver();
auto makeDrawRect = [] {
return CanvasOp<CanvasOpType::DrawRect>{
- .rect = SkRect::MakeWH(50, 50),
- .paint = SkPaint(SkColors::kBlack),
+ .rect = SkRect::MakeWH(50, 50),
+ .paint = SkPaint(SkColors::kBlack),
};
};
@@ -167,14 +167,14 @@
{
// First result should be identity
- const auto& result = transforms[0];
+ const auto &result = transforms[0];
EXPECT_EQ(SkMatrix::kIdentity_Mask, result.getType());
EXPECT_EQ(SkMatrix::I(), result);
}
{
// Should be translate 10, 10
- const auto& result = transforms[1];
+ const auto &result = transforms[1];
EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
SkMatrix m;
m.setTranslate(10, 10);
@@ -183,7 +183,7 @@
{
// Should be translate 10, 10 + scale 2, 4
- const auto& result = transforms[2];
+ const auto &result = transforms[2];
EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask, result.getType());
SkMatrix m;
m.setTranslate(10, 10);
@@ -193,7 +193,7 @@
{
// Should be translate 10, 10 + translate 20, 15
- const auto& result = transforms[3];
+ const auto &result = transforms[3];
EXPECT_EQ(SkMatrix::kTranslate_Mask, result.getType());
SkMatrix m;
m.setTranslate(30, 25);
@@ -202,9 +202,9 @@
{
// Should be translate 10, 10 + translate 20, 15 + rotate 90
- const auto& result = transforms[4];
+ const auto &result = transforms[4];
EXPECT_EQ(SkMatrix::kTranslate_Mask | SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask,
- result.getType());
+ result.getType());
SkMatrix m;
m.setTranslate(30, 25);
m.preRotate(90.f);
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 54970df..a718d46 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -149,7 +149,7 @@
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
size_t numPts = 3;
- auto pts = sk_ref_sp(
+ auto pts = sk_sp<Points>(
new Points({
{32, 16},
{48, 48},
@@ -192,7 +192,7 @@
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
size_t numPts = 3;
- auto pts = sk_ref_sp(
+ auto pts = sk_sp<Points>(
new Points({
{32, 16},
{48, 48},
diff --git a/libs/hwui/tests/unit/EglManagerTests.cpp b/libs/hwui/tests/unit/EglManagerTests.cpp
new file mode 100644
index 0000000..f7f2406
--- /dev/null
+++ b/libs/hwui/tests/unit/EglManagerTests.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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 <gtest/gtest.h>
+
+#include "renderthread/EglManager.h"
+#include "tests/common/TestContext.h"
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+TEST(EglManager, doesSurfaceLeak) {
+ EglManager eglManager;
+ eglManager.initialize();
+
+ ASSERT_TRUE(eglManager.hasEglContext());
+
+ auto colorSpace = SkColorSpace::MakeSRGB();
+ for (int i = 0; i < 100; i++) {
+ TestContext context;
+ auto result =
+ eglManager.createSurface(context.surface().get(), ColorMode::Default, colorSpace);
+ EXPECT_TRUE(result);
+ EGLSurface surface = result.unwrap();
+ eglManager.destroySurface(surface);
+ }
+
+ eglManager.destroy();
+}
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 6dd57b1..8c999c4 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -404,6 +404,7 @@
EXPECT_TRUE(pipeline->isSurfaceReady());
renderThread.destroyRenderingContext();
EXPECT_FALSE(pipeline->isSurfaceReady());
+ LOG_ALWAYS_FATAL_IF(pipeline->isSurfaceReady());
}
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 3cd615b..9774e80 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
/**
* An AudioProfile is specific to an audio format and lists supported sampling rates and
* channel masks for that format. An {@link AudioDeviceInfo} has a list of supported AudioProfiles.
@@ -63,4 +66,29 @@
public @NonNull int[] getSampleRates() {
return mSamplingRates;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
+ if (mSamplingRates != null && mSamplingRates.length > 0) {
+ sb.append(", sampling rates=").append(Arrays.toString(mSamplingRates));
+ }
+ if (mChannelMasks != null && mChannelMasks.length > 0) {
+ sb.append(", channel masks=").append(toHexString(mChannelMasks));
+ }
+ if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
+ sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String toHexString(int[] ints) {
+ if (ints == null || ints.length == 0) {
+ return "";
+ }
+ return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
+ .collect(Collectors.joining());
+ }
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cf31e41..d231c5f 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,7 +25,6 @@
import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.metrics.PlaybackComponent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -1539,7 +1538,7 @@
</tbody>
</table>
*/
-final public class MediaCodec implements PlaybackComponent {
+final public class MediaCodec {
/**
* Per buffer metadata includes an offset and size specifying
@@ -1682,7 +1681,6 @@
private MediaCodecInfo mCodecInfo;
private final Object mCodecInfoLock = new Object();
private MediaCrypto mCrypto;
- private String mPlaybackId;
private static final int EVENT_CALLBACK = 1;
private static final int EVENT_SET_CALLBACK = 2;
@@ -1693,23 +1691,6 @@
private static final int CB_ERROR = 3;
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
-
- /**
- * @hide
- */
- @Override
- public void setPlaybackId(@NonNull String playbackId) {
- // TODO: add a native method to pass the ID to the native code for logging.
- mPlaybackId = playbackId;
- }
- /**
- * @hide
- */
- @Override
- public String getPlaybackId() {
- return mPlaybackId;
- }
-
private class EventHandler extends Handler {
private MediaCodec mCodec;
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 8f60330..209f0c02 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.media.metrics.PlaybackComponent;
import android.net.Uri;
import android.os.IBinder;
import android.os.IHwBinder;
@@ -40,6 +41,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -73,8 +75,10 @@
* <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
* when used with network-based content.
*/
-final public class MediaExtractor {
+public final class MediaExtractor implements PlaybackComponent {
+
public MediaExtractor() {
+ mPlaybackId = "";
native_setup();
}
@@ -767,6 +771,18 @@
*/
public native boolean hasCacheReachedEndOfStream();
+ @Override
+ public void setPlaybackId(@NonNull String playbackId) {
+ mPlaybackId = Objects.requireNonNull(playbackId);
+ native_setPlaybackId(playbackId);
+ }
+
+ @NonNull
+ @Override
+ public String getPlaybackId() {
+ return mPlaybackId;
+ }
+
/**
* Return Metrics data about the current media container.
*
@@ -784,6 +800,7 @@
return bundle;
}
+ private native void native_setPlaybackId(String playbackId);
private native PersistableBundle native_getMetrics();
private static native final void native_init();
@@ -796,6 +813,7 @@
}
private MediaCas mMediaCas;
+ private String mPlaybackId;
private long mNativeContext;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index f8a642a..1242c06 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -215,6 +215,15 @@
private Map<String, Object> mMap;
/**
+ * A key describing the log session ID for MediaCodec. The log session ID is a random 32-byte
+ * hexadecimal string that is used to associate metrics from multiple media codec instances
+ * to the same playback or recording session.
+ * The associated value is a string.
+ * @hide
+ */
+ public static final String LOG_SESSION_ID = "log-session-id";
+
+ /**
* A key describing the mime type of the MediaFormat.
* The associated value is a string.
*/
@@ -479,6 +488,8 @@
* <li>The timestampUs shall be monotonically increasing.</li>
* <li>The timestampUs shall fall within the time span of the video track.</li>
* <li>The first timestampUs should match that of the first video sample.</li>
+ *
+ * @hide
*/
public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 9176dae..1b32b5b 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.BIND_IMS_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -4300,7 +4299,7 @@
@RequiresPermission(BIND_IMS_SERVICE)
public void setOnRtpRxNoticeListener(
@NonNull Context context,
- @NonNull @CallbackExecutor Executor executor,
+ @NonNull Executor executor,
@NonNull OnRtpRxNoticeListener listener) {
Objects.requireNonNull(context);
Preconditions.checkArgument(
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 49a4cc6..27e9bfe 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -149,6 +149,19 @@
}
/**
+ * Sets the log session ID for MediaRecorder.
+ *
+ * <p>The log session ID is a random 32-byte hexadecimal string that is used for monitoring the
+ * MediaRecorder performance.</p>
+ *
+ * @param id the global ID for monitoring the MediaRecorder performance
+ * @hide
+ */
+ public void setLogSessionId(@NonNull String id) {
+ setParameter("log-session-id=" + id);
+ }
+
+ /**
* Sets a {@link android.hardware.Camera} to use for recording.
*
* <p>Use this function to switch quickly between preview and capture mode without a teardown of
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6b329f8..b1baf94 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -23,6 +23,8 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -39,6 +41,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
+import android.util.Xml;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -99,6 +102,7 @@
private int mSurfaceWidth;
private int mSurfaceHeight;
private final AttributeSet mAttrs;
+ private final XmlResourceParser mParser;
private final int mDefStyleAttr;
private int mWindowZOrder;
private boolean mUseRequestedSurfaceLayout;
@@ -168,7 +172,16 @@
public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mAttrs = attrs;
+ int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+ if (sourceResId != Resources.ID_NULL) {
+ Log.d(TAG, "Build local AttributeSet");
+ mParser = context.getResources().getXml(sourceResId);
+ mAttrs = Xml.asAttributeSet(mParser);
+ } else {
+ Log.d(TAG, "Use passed in AttributeSet");
+ mParser = null;
+ mAttrs = attrs;
+ }
mDefStyleAttr = defStyleAttr;
resetSurfaceView();
mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index ee70714..e8ef464 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -46,6 +46,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -112,21 +113,34 @@
/**
* All the info about a connection.
*/
- private class ConnectionRecord implements IBinder.DeathRecipient {
- String pkg;
- int uid;
- int pid;
- Bundle rootHints;
- IMediaBrowserServiceCallbacks callbacks;
- BrowserRoot root;
- HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>();
+ private static class ConnectionRecord implements IBinder.DeathRecipient {
+ public final MediaBrowserService service;
+ public final String pkg;
+ public final int pid;
+ public final int uid;
+ public final Bundle rootHints;
+ public final IMediaBrowserServiceCallbacks callbacks;
+ public final BrowserRoot root;
+ public final HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>();
+
+ ConnectionRecord(
+ MediaBrowserService service, String pkg, int pid, int uid, Bundle rootHints,
+ IMediaBrowserServiceCallbacks callbacks, BrowserRoot root) {
+ this.service = service;
+ this.pkg = pkg;
+ this.pid = pid;
+ this.uid = uid;
+ this.rootHints = rootHints;
+ this.callbacks = callbacks;
+ this.root = root;
+ }
@Override
public void binderDied() {
- mHandler.post(new Runnable() {
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
- mConnections.remove(callbacks.asBinder());
+ service.mConnections.remove(callbacks.asBinder());
}
});
}
@@ -199,39 +213,46 @@
}
}
- private class ServiceBinder extends IMediaBrowserService.Stub {
+ private static class ServiceBinder extends IMediaBrowserService.Stub {
+ private WeakReference<MediaBrowserService> mService;
+
+ private ServiceBinder(MediaBrowserService service) {
+ mService = new WeakReference(service);
+ }
+
@Override
public void connect(final String pkg, final Bundle rootHints,
final IMediaBrowserServiceCallbacks callbacks) {
+ MediaBrowserService service = mService.get();
+ if (service == null) {
+ return;
+ }
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- if (!isValidPackage(pkg, uid)) {
+ if (!service.isValidPackage(pkg, uid)) {
throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
+ " package=" + pkg);
}
- mHandler.post(new Runnable() {
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
// Clear out the old subscriptions. We are getting new ones.
- mConnections.remove(b);
+ service.mConnections.remove(b);
- final ConnectionRecord connection = new ConnectionRecord();
- connection.pkg = pkg;
- connection.pid = pid;
- connection.uid = uid;
- connection.rootHints = rootHints;
- connection.callbacks = callbacks;
-
- mCurConnection = connection;
- connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
- mCurConnection = null;
+ // Temporarily sets a placeholder ConnectionRecord to make
+ // getCurrentBrowserInfo() work in onGetRoot().
+ service.mCurConnection =
+ new ConnectionRecord(
+ service, pkg, pid, uid, rootHints, callbacks, null);
+ BrowserRoot root = service.onGetRoot(pkg, uid, rootHints);
+ service.mCurConnection = null;
// If they didn't return something, don't allow this client.
- if (connection.root == null) {
+ if (root == null) {
Log.i(TAG, "No root for client " + pkg + " from service "
+ getClass().getName());
try {
@@ -242,16 +263,19 @@
}
} else {
try {
- mConnections.put(b, connection);
+ ConnectionRecord connection =
+ new ConnectionRecord(
+ service, pkg, pid, uid, rootHints, callbacks, root);
+ service.mConnections.put(b, connection);
b.linkToDeath(connection, 0);
- if (mSession != null) {
+ if (service.mSession != null) {
callbacks.onConnect(connection.root.getRootId(),
- mSession, connection.root.getExtras());
+ service.mSession, connection.root.getExtras());
}
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. "
+ "pkg=" + pkg);
- mConnections.remove(b);
+ service.mConnections.remove(b);
}
}
}
@@ -260,13 +284,18 @@
@Override
public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
- mHandler.post(new Runnable() {
+ MediaBrowserService service = mService.get();
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
// Clear out the old subscriptions. We are getting new ones.
- final ConnectionRecord old = mConnections.remove(b);
+ final ConnectionRecord old = service.mConnections.remove(b);
if (old != null) {
// TODO
old.callbacks.asBinder().unlinkToDeath(old, 0);
@@ -283,20 +312,25 @@
@Override
public void addSubscription(final String id, final IBinder token, final Bundle options,
final IMediaBrowserServiceCallbacks callbacks) {
- mHandler.post(new Runnable() {
+ MediaBrowserService service = mService.get();
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
// Get the record for the connection
- final ConnectionRecord connection = mConnections.get(b);
+ final ConnectionRecord connection = service.mConnections.get(b);
if (connection == null) {
Log.w(TAG, "addSubscription for callback that isn't registered id="
+ id);
return;
}
- MediaBrowserService.this.addSubscription(id, connection, token, options);
+ service.addSubscription(id, connection, token, options);
}
});
}
@@ -310,18 +344,23 @@
@Override
public void removeSubscription(final String id, final IBinder token,
final IMediaBrowserServiceCallbacks callbacks) {
- mHandler.post(new Runnable() {
+ MediaBrowserService service = mService.get();
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
- ConnectionRecord connection = mConnections.get(b);
+ ConnectionRecord connection = service.mConnections.get(b);
if (connection == null) {
Log.w(TAG, "removeSubscription for callback that isn't registered id="
+ id);
return;
}
- if (!MediaBrowserService.this.removeSubscription(id, connection, token)) {
+ if (!service.removeSubscription(id, connection, token)) {
Log.w(TAG, "removeSubscription called for " + id
+ " which is not subscribed");
}
@@ -332,16 +371,21 @@
@Override
public void getMediaItem(final String mediaId, final ResultReceiver receiver,
final IMediaBrowserServiceCallbacks callbacks) {
- mHandler.post(new Runnable() {
+ MediaBrowserService service = mService.get();
+ if (service == null) {
+ return;
+ }
+
+ service.mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
- ConnectionRecord connection = mConnections.get(b);
+ ConnectionRecord connection = service.mConnections.get(b);
if (connection == null) {
Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId);
return;
}
- performLoadItem(mediaId, connection, receiver);
+ service.performLoadItem(mediaId, connection, receiver);
}
});
}
@@ -350,7 +394,7 @@
@Override
public void onCreate() {
super.onCreate();
- mBinder = new ServiceBinder();
+ mBinder = new ServiceBinder(this);
}
@Override
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 6a622c5a..91178e5 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -282,7 +282,6 @@
return status;
}
-
status_t JMediaExtractor::getSampleMeta(sp<MetaData> *sampleMeta) {
return mImpl->getSampleMeta(sampleMeta);
}
@@ -295,6 +294,10 @@
AudioPresentationCollection *presentations) const {
return mImpl->getAudioPresentations(trackIdx, presentations);
}
+
+status_t JMediaExtractor::setPlaybackId(const String8 &playbackId) {
+ return mImpl->setPlaybackId(playbackId);
+}
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -920,6 +923,23 @@
return mybundle;
}
+static void
+android_media_MediaExtractor_native_setPlaybackId(
+ JNIEnv * env, jobject thiz, jstring playbackIdJString)
+{
+ ALOGV("android_media_MediaExtractor_native_setPlaybackId");
+
+ sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+ if (extractor == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", nullptr);
+ }
+
+ const char* playbackId = env->GetStringUTFChars(playbackIdJString, nullptr);
+ if (extractor->setPlaybackId(String8(playbackId)) != OK) {
+ ALOGE("setPlaybackId failed");
+ }
+ env->ReleaseStringUTFChars(playbackIdJString, playbackId);
+}
static const JNINativeMethod gMethods[] = {
{ "release", "()V", (void *)android_media_MediaExtractor_release },
@@ -990,6 +1010,9 @@
{"native_getMetrics", "()Landroid/os/PersistableBundle;",
(void *)android_media_MediaExtractor_native_getMetrics},
+ { "native_setPlaybackId", "(Ljava/lang/String;)V",
+ (void *)android_media_MediaExtractor_native_setPlaybackId},
+
{ "native_getAudioPresentations", "(I)Ljava/util/List;",
(void *)android_media_MediaExtractor_getAudioPresentations },
};
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index f5ba92e..9aaa643 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -70,6 +70,8 @@
status_t getAudioPresentations(size_t trackIdx,
AudioPresentationCollection *presentations) const;
+ status_t setPlaybackId(const String8& playbackId);
+
protected:
virtual ~JMediaExtractor();
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index d10ff40..4c3ffb6 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -214,6 +214,10 @@
<!-- Default for Settings.System.VIBRATE_WHEN_RINGING -->
<bool name="def_vibrate_when_ringing">false</bool>
+ <!-- Default for Settings.Global.CELL_ON; see PhoneConstants.CELL_ON_FLAG.
+ 0: cellular off; 1: cellular on. -->
+ <integer name="def_cell_on">1</integer>
+
<!-- Default for Settings.Global.APPLY_RAMPING_RINGER -->
<bool name="def_apply_ramping_ringer">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 6568bff..ba4f7e1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2467,6 +2467,9 @@
loadBooleanSetting(stmt, Settings.Global.BLUETOOTH_ON,
R.bool.def_bluetooth_on);
+ loadIntegerSetting(stmt, Settings.Global.CELL_ON,
+ R.integer.def_cell_on);
+
// Enable or disable Cell Broadcast SMS
loadSetting(stmt, Settings.Global.CDMA_CELL_BROADCAST_SMS,
RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4dc6d14..bde2b9f 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -751,7 +751,8 @@
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
Settings.Secure.SUPPRESS_DOZE,
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+ Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+ Settings.Secure.TRANSFORM_ENABLED);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml
new file mode 100644
index 0000000..ef67e51
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/menu_background_item">
+ <shape android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
+ android:bottomRightRadius="0dp"
+ android:topLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
+ android:topRightRadius="0dp"/>
+ <solid
+ android:color="@color/accessibility_floating_menu_background"/>
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
new file mode 100644
index 0000000..f7357b2
--- /dev/null
+++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/accessibility_floating_menu_padding"
+ android:paddingEnd="@dimen/accessibility_floating_menu_padding"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <View
+ android:id="@+id/icon_view"
+ android:layout_width="@dimen/accessibility_floating_menu_small_width_height"
+ android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/>
+
+ <View
+ android:id="@+id/transparent_divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/accessibility_floating_menu_padding"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 37ec576..d571f2f 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -102,4 +102,6 @@
<color name="privacy_circle_camera">#81C995</color> <!-- g300 -->
<color name="privacy_circle_microphone_location">#FCAD70</color> <!--o300 -->
+ <!-- Accessibility floating menu -->
+ <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3bc1c80..8da25d5 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -274,4 +274,8 @@
<!-- TODO(b/178093014) Colors for privacy dialog. These should be changed to the new palette -->
<color name="privacy_circle_camera">#1E8E3E</color> <!-- g600 -->
<color name="privacy_circle_microphone_location">#E8710A</color> <!--o600 -->
+
+ <!-- Accessibility floating menu -->
+ <color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
+ <color name="accessibility_floating_menu_stroke_dark">#26FFFFFF</color> <!-- 15% -->
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ea0ea5e..0ff0b90 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1361,6 +1361,19 @@
<dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
+ <!-- Accessibility floating menu -->
+ <dimen name="accessibility_floating_menu_elevation">5dp</dimen>
+ <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen>
+ <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen>
+ <dimen name="accessibility_floating_menu_margin">16dp</dimen>
+ <dimen name="accessibility_floating_menu_padding">6dp</dimen>
+ <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen>
+ <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen>
+ <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen>
+ <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
+ <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
+ <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+
<dimen name="rounded_slider_height">48dp</dimen>
<!-- rounded_slider_height / 2 -->
<dimen name="rounded_slider_corner_radius">24dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
new file mode 100644
index 0000000..b71e135
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Contains logic for an accessibility floating menu view.
+ */
+public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
+ private static final int DEFAULT_FADE_EFFECT_ENABLED = 1;
+ private static final float DEFAULT_OPACITY_VALUE = 0.55f;
+ private final Context mContext;
+ private final AccessibilityFloatingMenuView mMenuView;
+
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ }
+ };
+
+ private final ContentObserver mSizeContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.setSizeType(getSizeType(mContext));
+ }
+ };
+
+ private final ContentObserver mShapeContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.setShapeType(getShapeType(mContext));
+ }
+ };
+
+ private final ContentObserver mFadeOutContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
+ getOpacityValue(mContext));
+ }
+ };
+
+ public AccessibilityFloatingMenu(Context context) {
+ mContext = context;
+ mMenuView = new AccessibilityFloatingMenuView(context);
+ }
+
+ @VisibleForTesting
+ AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) {
+ mContext = context;
+ mMenuView = menuView;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mMenuView.isShowing();
+ }
+
+ @Override
+ public void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ mMenuView.show();
+ mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
+ getOpacityValue(mContext));
+ mMenuView.setSizeType(getSizeType(mContext));
+ mMenuView.setShapeType(getShapeType(mContext));
+
+ registerContentObservers();
+ }
+
+ @Override
+ public void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mMenuView.hide();
+
+ unregisterContentObservers();
+ }
+
+ private static boolean isFadeEffectEnabled(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ DEFAULT_FADE_EFFECT_ENABLED) == /* enable */ 1;
+ }
+
+ private static float getOpacityValue(Context context) {
+ return Settings.Secure.getFloat(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_OPACITY,
+ DEFAULT_OPACITY_VALUE);
+ }
+
+ private static int getSizeType(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
+ }
+
+ private static int getShapeType(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ ShapeType.CIRCLE);
+ }
+
+ private void registerContentObservers() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ /* notifyForDescendants */ false, mContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+ /* notifyForDescendants */ false, mSizeContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE),
+ /* notifyForDescendants */ false, mShapeContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ /* notifyForDescendants */ false, mFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ /* notifyForDescendants */ false, mFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void unregisterContentObservers() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mShapeContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
new file mode 100644
index 0000000..3aed1d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.DimenRes;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Accessibility floating menu is used for the actions of accessibility features, it's also the
+ * action set.
+ *
+ * <p>The number of items would depend on strings key
+ * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
+ */
+public class AccessibilityFloatingMenuView extends FrameLayout
+ implements RecyclerView.OnItemTouchListener {
+ private static final float DEFAULT_LOCATION_Y_PERCENTAGE = 0.8f;
+ private static final int INDEX_MENU_ITEM = 0;
+ private static final int FADE_OUT_DURATION_MS = 1000;
+ private static final int FADE_EFFECT_DURATION_MS = 3000;
+
+ private boolean mIsFadeEffectEnabled;
+ private boolean mIsShowing;
+ @SizeType
+ private int mSizeType = SizeType.SMALL;
+ private int mMargin;
+ private int mPadding;
+ private int mScreenHeight;
+ private int mScreenWidth;
+ private int mIconWidth;
+ private int mIconHeight;
+ private final RecyclerView mListView;
+ private final AccessibilityTargetAdapter mAdapter;
+ private float mFadeOutValue;
+ private final ValueAnimator mFadeOutAnimator;
+ private final Handler mUiHandler;
+ private final WindowManager.LayoutParams mLayoutParams;
+ private final WindowManager mWindowManager;
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @IntDef({
+ SizeType.SMALL,
+ SizeType.LARGE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SizeType {
+ int SMALL = 0;
+ int LARGE = 1;
+ }
+
+ @IntDef({
+ ShapeType.CIRCLE,
+ ShapeType.HALF_CIRCLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ShapeType {
+ int CIRCLE = 0;
+ int HALF_CIRCLE = 1;
+ }
+
+ public AccessibilityFloatingMenuView(Context context) {
+ this(context, new RecyclerView(context));
+ }
+
+ @VisibleForTesting
+ AccessibilityFloatingMenuView(Context context,
+ RecyclerView listView) {
+ super(context);
+
+ mListView = listView;
+ mWindowManager = context.getSystemService(WindowManager.class);
+ mLayoutParams = createDefaultLayoutParams();
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
+ mUiHandler = createUiHandler();
+
+ mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
+ mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
+ mFadeOutAnimator.addUpdateListener(
+ (animation) -> setAlpha((float) animation.getAnimatedValue()));
+
+ updateDimensions();
+ initListView();
+
+ final int uiMode = context.getResources().getConfiguration().uiMode;
+ updateStrokeWith(uiMode);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent event) {
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ fadeIn();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Do nothing
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ fadeOut();
+ break;
+ default: // Do nothing
+ }
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
+ // Do Nothing
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean b) {
+ // Do Nothing
+ }
+
+ void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ mIsShowing = true;
+ mWindowManager.addView(this, mLayoutParams);
+ }
+
+ void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mIsShowing = false;
+ mWindowManager.removeView(this);
+ }
+
+ boolean isShowing() {
+ return mIsShowing;
+ }
+
+ void onTargetsChanged(List<AccessibilityTarget> newTargets) {
+ fadeIn();
+
+ mTargets.clear();
+ mTargets.addAll(newTargets);
+ mAdapter.notifyDataSetChanged();
+
+ updateRadiusWith(mSizeType, mTargets.size());
+
+ fadeOut();
+ }
+
+ void setSizeType(@SizeType int newSizeType) {
+ fadeIn();
+
+ mSizeType = newSizeType;
+
+ updateIconSizeWith(newSizeType);
+ updateRadiusWith(newSizeType, mTargets.size());
+
+ // When the icon sized changed, the menu size and location will be impacted.
+ updateLocation();
+
+ fadeOut();
+ }
+
+ void setShapeType(@ShapeType int newShapeType) {
+ fadeIn();
+
+ final boolean isCircleShape =
+ newShapeType == ShapeType.CIRCLE;
+ final float offset =
+ isCircleShape
+ ? 0
+ : getLayoutWidth() / 2.0f;
+ mListView.animate().translationX(offset);
+
+ setOnTouchListener(
+ isCircleShape
+ ? null
+ : (view, event) -> onTouched(event));
+
+ fadeOut();
+ }
+
+ void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
+ mIsFadeEffectEnabled = isFadeEffectEnabled;
+ mFadeOutValue = newOpacityValue;
+
+ mFadeOutAnimator.cancel();
+ mFadeOutAnimator.setFloatValues(1.0f, mFadeOutValue);
+ setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
+ }
+
+ @VisibleForTesting
+ void fadeIn() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ mUiHandler.removeCallbacksAndMessages(null);
+ mUiHandler.post(() -> setAlpha(/* completely opaque */ 1.0f));
+ }
+
+ @VisibleForTesting
+ void fadeOut() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
+ }
+
+ private boolean onTouched(MotionEvent event) {
+ final int currentX = (int) event.getX();
+ final int currentY = (int) event.getY();
+
+ final int menuHalfWidth = getLayoutWidth() / 2;
+ final Rect touchDelegateBounds =
+ new Rect(mMargin, mMargin, mMargin + menuHalfWidth, mMargin + getLayoutHeight());
+ if (touchDelegateBounds.contains(currentX, currentY)) {
+ // In order to correspond to the correct item of list view.
+ event.setLocation(currentX - mMargin, currentY - mMargin);
+ return mListView.dispatchTouchEvent(event);
+ }
+
+ return false;
+ }
+
+ private void setRadius(float radius) {
+ final float[] radii = new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ getMenuGradientDrawable().setCornerRadii(radii);
+ }
+
+ private Handler createUiHandler() {
+ final Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+ return new Handler(looper);
+ }
+
+ private void updateDimensions() {
+ final Resources res = getResources();
+ final DisplayMetrics dm = res.getDisplayMetrics();
+ mScreenWidth = dm.widthPixels;
+ mScreenHeight = dm.heightPixels;
+ mMargin =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
+ mPadding =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
+ }
+
+ private void updateIconSizeWith(@SizeType int sizeType) {
+ final Resources res = getResources();
+ final int iconResId =
+ sizeType == SizeType.SMALL
+ ? R.dimen.accessibility_floating_menu_small_width_height
+ : R.dimen.accessibility_floating_menu_large_width_height;
+ mIconWidth = res.getDimensionPixelSize(iconResId);
+ mIconHeight = mIconWidth;
+
+ mAdapter.setIconWidthHeight(mIconWidth);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void initListView() {
+ final Drawable listViewBackground =
+ getContext().getDrawable(R.drawable.accessibility_floating_menu_background);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
+ mListView.setBackground(listViewBackground);
+ mListView.setAdapter(mAdapter);
+ mListView.setLayoutManager(layoutManager);
+ mListView.addOnItemTouchListener(this);
+ updateListView();
+
+ addView(mListView);
+ }
+
+ private void updateListView() {
+ final int elevation =
+ getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
+ final LayoutParams layoutParams =
+ new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ layoutParams.setMarginsRelative(mMargin, mMargin, /* end= */ 0, mMargin);
+ mListView.setLayoutParams(layoutParams);
+ mListView.setElevation(elevation);
+ }
+
+ private WindowManager.LayoutParams createDefaultLayoutParams() {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ final DisplayMetrics dm = getResources().getDisplayMetrics();
+ params.gravity = Gravity.START | Gravity.TOP;
+ params.x = dm.widthPixels;
+ params.y = (int) (dm.heightPixels * DEFAULT_LOCATION_Y_PERCENTAGE);
+
+ return params;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateDimensions();
+ updateListView();
+ updateIconSizeWith(mSizeType);
+ updateColor();
+ updateStrokeWith(newConfig.uiMode);
+ updateLocation();
+ }
+
+ private LayerDrawable getMenuLayerDrawable() {
+ return (LayerDrawable) mListView.getBackground();
+ }
+
+ private GradientDrawable getMenuGradientDrawable() {
+ return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
+ }
+
+ /**
+ * Updates the floating menu to be fixed at the side of the screen.
+ */
+ private void updateLocation() {
+ mLayoutParams.x = mScreenWidth - mMargin - getLayoutWidth();
+ mLayoutParams.y = (int) (mScreenHeight * DEFAULT_LOCATION_Y_PERCENTAGE);
+ mWindowManager.updateViewLayout(this, mLayoutParams);
+ }
+
+ private void updateColor() {
+ final int menuColorResId = R.color.accessibility_floating_menu_background;
+ getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId));
+ }
+
+ private void updateStrokeWith(int uiMode) {
+ final Resources res = getResources();
+ final boolean isNightMode =
+ (uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+
+ final int inset =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
+ final int insetRight = isNightMode ? inset : 0;
+ getMenuLayerDrawable().setLayerInset(INDEX_MENU_ITEM, 0, 0, insetRight, 0);
+
+ final int width =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width);
+ final int strokeWidth = isNightMode ? width : 0;
+ final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark);
+ getMenuGradientDrawable().setStroke(strokeWidth, strokeColor);
+ }
+
+ private void updateRadiusWith(@SizeType int sizeType, int itemCount) {
+ setRadius(getResources().getDimensionPixelSize(getRadiusResId(sizeType, itemCount)));
+ }
+
+ private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
+ return sizeType == SizeType.SMALL
+ ? getSmallSizeResIdWith(itemCount)
+ : getLargeSizeResIdWith(itemCount);
+ }
+
+ private int getSmallSizeResIdWith(int itemCount) {
+ return itemCount > 1
+ ? R.dimen.accessibility_floating_menu_small_multiple_radius
+ : R.dimen.accessibility_floating_menu_small_single_radius;
+ }
+
+ private int getLargeSizeResIdWith(int itemCount) {
+ return itemCount > 1
+ ? R.dimen.accessibility_floating_menu_large_multiple_radius
+ : R.dimen.accessibility_floating_menu_large_single_radius;
+ }
+
+ private int getLayoutWidth() {
+ return mPadding * 2 + mIconWidth;
+ }
+
+ private int getLayoutHeight() {
+ return (mPadding + mIconHeight) * mTargets.size() + mPadding;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
new file mode 100644
index 0000000..bb4038e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.systemui.accessibility.floatingmenu;
+
+import static android.view.View.GONE;
+
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.FIRST_ITEM;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.LAST_ITEM;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.REGULAR_ITEM;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * An adapter which shows the set of accessibility targets that can be performed.
+ */
+public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
+ private int mIconWidthHeight;
+ private final List<AccessibilityTarget> mTargets;
+
+ @IntDef({
+ FIRST_ITEM,
+ REGULAR_ITEM,
+ LAST_ITEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ItemType {
+ int FIRST_ITEM = 0;
+ int REGULAR_ITEM = 1;
+ int LAST_ITEM = 2;
+ }
+
+ public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
+ mTargets = targets;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, @ItemType int itemType) {
+ final View root = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.accessibility_floating_menu_item, parent,
+ /* attachToRoot= */ false);
+
+ if (itemType == FIRST_ITEM) {
+ return new TopViewHolder(root);
+ }
+
+ if (itemType == LAST_ITEM) {
+ return new BottomViewHolder(root);
+ }
+
+ return new ViewHolder(root);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.mIconView.setBackground(mTargets.get(position).getIcon());
+ holder.updateIconWidthHeight(mIconWidthHeight);
+ holder.itemView.setOnClickListener((v) -> mTargets.get(position).onSelected());
+ }
+
+ @ItemType
+ @Override
+ public int getItemViewType(int position) {
+ if (position == 0) {
+ return FIRST_ITEM;
+ }
+
+ if (position == (getItemCount() - 1)) {
+ return LAST_ITEM;
+ }
+
+ return REGULAR_ITEM;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTargets.size();
+ }
+
+ public void setIconWidthHeight(int iconWidthHeight) {
+ mIconWidthHeight = iconWidthHeight;
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ final View mIconView;
+ final View mDivider;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ mIconView = itemView.findViewById(R.id.icon_view);
+ mDivider = itemView.findViewById(R.id.transparent_divider);
+ }
+
+ void updateIconWidthHeight(int newValue) {
+ final ViewGroup.LayoutParams layoutParams = mIconView.getLayoutParams();
+ if (layoutParams.width == newValue) {
+ return;
+ }
+ layoutParams.width = newValue;
+ layoutParams.height = newValue;
+ mIconView.setLayoutParams(layoutParams);
+ }
+ }
+
+ static class TopViewHolder extends ViewHolder {
+ TopViewHolder(View itemView) {
+ super(itemView);
+ final int padding = itemView.getPaddingStart();
+ itemView.setPaddingRelative(padding, padding, padding, 0);
+ }
+ }
+
+ static class BottomViewHolder extends ViewHolder {
+ BottomViewHolder(View itemView) {
+ super(itemView);
+ mDivider.setVisibility(GONE);
+ final int padding = itemView.getPaddingStart();
+ itemView.setPaddingRelative(padding, 0, padding, padding);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java
new file mode 100644
index 0000000..62f02a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+/**
+ * Interface for managing the accessibility targets menu component.
+ */
+public interface IAccessibilityFloatingMenu {
+
+ /**
+ * Checks if the menu was shown.
+ */
+ boolean isShowing();
+
+ /**
+ * Shows the accessibility targets menu.
+ */
+ void show();
+
+ /**
+ * Hides the accessibility targets menu.
+ */
+ void hide();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
new file mode 100644
index 0000000..9b3e386
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
@@ -0,0 +1,12 @@
+# Scroll Capture (Long Screenshots)
+# Bug component: 801322
+#
+# Referenced by:
+#
+# core/java/src/android/view/OWNERS
+# core/java/src/com/android/internal/view/OWNERS
+# core/tests/coretests/src/android/view/OWNERS
+# core/tests/coretests/src/com/android/internal/view/OWNERS
+
+mrcasey@google.com
+mrenouf@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 3db5440..d6356de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -242,12 +242,12 @@
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
- if (mBubblesOptional.isPresent() && !mBubblesOptional.get()
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get()
.isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getSbn().getGroupKey())) {
- childCount++;
- } else {
hasBubbles = true;
+ } else {
+ childCount++;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f192287..f931b89 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -169,6 +169,11 @@
},
UserHandle.USER_ALL);
+ // All wallpaper color and keyguard logic only applies when Monet is enabled.
+ if (!mIsMonetEnabled) {
+ return;
+ }
+
// Upon boot, make sure we have the most up to date colors
mBgExecutor.execute(() -> {
WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 4eb75eb..19d8d3c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -268,6 +268,7 @@
public void onStop() {
mSysUiMainExecutor.execute(() -> {
if (oneHanded.isOneHandedEnabled()) {
+ // Log metrics for 3-button navigation mode.
oneHanded.stopOneHanded(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT);
} else if (oneHanded.isSwipeToNotificationEnabled()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
new file mode 100644
index 0000000..337d97e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityFloatingMenu}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AccessibilityFloatingMenuTest extends SysuiTestCase {
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ private AccessibilityFloatingMenuView mMenuView;
+ private AccessibilityFloatingMenu mMenu;
+
+ @Before
+ public void initMenu() {
+ MockitoAnnotations.initMocks(this);
+
+ final List<AccessibilityTarget> mTargets = new ArrayList<>();
+ mTargets.add(mock(AccessibilityTarget.class));
+
+ final List<String> assignedTargets = new ArrayList<>();
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+ assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+ doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
+ anyInt());
+
+ mMenuView = new AccessibilityFloatingMenuView(mContext);
+ mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
+ }
+
+ @Test
+ public void showMenuView_success() {
+ mMenu.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ }
+
+ @Test
+ public void hideMenuView_success() {
+ mMenu.show();
+ mMenu.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ }
+
+ @After
+ public void tearDown() {
+ mMenu.hide();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
new file mode 100644
index 0000000..124a749
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityFloatingMenuView}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
+ private AccessibilityFloatingMenuView mMenuView;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private ViewPropertyAnimator mAnimator;
+
+ private RecyclerView mListView;
+
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
+ mWindowManager).getMaximumWindowMetrics();
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
+ mTargets.add(mock(AccessibilityTarget.class));
+ mListView = spy(new RecyclerView(mContext));
+ mMenuView = spy(new AccessibilityFloatingMenuView(mContext));
+ }
+
+ @Test
+ public void initListView_success() {
+ assertThat(mMenuView.getChildCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void showMenuView_success() {
+ mMenuView.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ verify(mWindowManager).addView(eq(mMenuView), any(WindowManager.LayoutParams.class));
+ }
+
+ @Test
+ public void showMenuView_showTwice_addViewOnce() {
+ mMenuView.show();
+ mMenuView.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ verify(mWindowManager, times(1)).addView(eq(mMenuView),
+ any(WindowManager.LayoutParams.class));
+ }
+
+ @Test
+ public void hideMenuView_success() {
+ mMenuView.show();
+ mMenuView.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ verify(mWindowManager).removeView(eq(mMenuView));
+ }
+
+ @Test
+ public void hideMenuView_hideTwice_removeViewOnce() {
+ mMenuView.show();
+ mMenuView.hide();
+ mMenuView.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ verify(mWindowManager, times(1)).removeView(eq(mMenuView));
+ }
+
+ @Test
+ public void updateListViewRadius_singleTarget_matchResult() {
+ final float radius =
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_small_single_radius);
+ final float[] expectedRadii =
+ new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+
+ mMenuView.onTargetsChanged(mTargets);
+ final View view = mMenuView.getChildAt(0);
+ final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground();
+ final GradientDrawable gradientDrawable =
+ (GradientDrawable) layerDrawable.getDrawable(0);
+ final float[] actualRadii = gradientDrawable.getCornerRadii();
+
+ assertThat(actualRadii).isEqualTo(expectedRadii);
+ }
+
+ @Test
+ public void setSizeType_largeSize_matchResult() {
+ final int shapeType = 2;
+ final float radius = getContext().getResources().getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_large_single_radius);
+ final float[] expectedRadii =
+ new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ final Drawable listViewBackground =
+ mContext.getDrawable(R.drawable.accessibility_floating_menu_background);
+ mListView = spy(new RecyclerView(mContext));
+ mListView.setBackground(listViewBackground);
+
+ mMenuView = new AccessibilityFloatingMenuView(mContext, mListView);
+ mMenuView.setSizeType(shapeType);
+ final LayerDrawable layerDrawable =
+ (LayerDrawable) mListView.getBackground();
+ final GradientDrawable gradientDrawable =
+ (GradientDrawable) layerDrawable.getDrawable(0);
+
+ assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
+ }
+
+ @Test
+ public void setShapeType_halfCircle_translationX() {
+ final int shapeType = 2;
+ doReturn(mAnimator).when(mListView).animate();
+
+ mMenuView = new AccessibilityFloatingMenuView(mContext, mListView);
+ mMenuView.setShapeType(shapeType);
+
+ verify(mAnimator).translationX(anyFloat());
+ }
+
+ @Test
+ public void onTargetsChanged_fadeInOut() {
+ mMenuView.onTargetsChanged(mTargets);
+
+ verify(mMenuView).fadeIn();
+ verify(mMenuView).fadeOut();
+ }
+
+ @Test
+ public void setSizeType_fadeInOut() {
+ final int smallSize = 0;
+ mMenuView.setSizeType(smallSize);
+
+ verify(mMenuView).fadeIn();
+ verify(mMenuView).fadeOut();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
new file mode 100644
index 0000000..899625e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 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.systemui.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityTargetAdapter}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AccessibilityTargetAdapterTest extends SysuiTestCase {
+ @Mock
+ private AccessibilityTarget mAccessibilityTarget;
+
+ @Mock
+ private Drawable mIcon;
+
+ @Mock
+ private Drawable.ConstantState mConstantState;
+
+ private ViewHolder mViewHolder;
+ private AccessibilityTargetAdapter mAdapter;
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTargets.add(mAccessibilityTarget);
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
+
+ final View root = LayoutInflater.from(mContext).inflate(
+ R.layout.accessibility_floating_menu_item, null);
+ mViewHolder = new ViewHolder(root);
+ when(mAccessibilityTarget.getIcon()).thenReturn(mIcon);
+ when(mIcon.getConstantState()).thenReturn(mConstantState);
+ }
+
+ @Test
+ public void onBindViewHolder_setIconWidthHeight_matchResult() {
+ final int iconWidthHeight = 50;
+ mAdapter.setIconWidthHeight(iconWidthHeight);
+
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+ final int actualIconWith = mViewHolder.mIconView.getLayoutParams().width;
+
+ assertThat(actualIconWith).isEqualTo(iconWidthHeight);
+ }
+}
diff --git a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
index a18ebb3..690d0a0 100644
--- a/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
+++ b/packages/overlays/IconPackVictorThemePickerOverlay/Android.bp
@@ -26,6 +26,5 @@
runtime_resource_overlay {
name: "IconPackVictorThemePickerOverlay",
theme: "IconPackVictorThemePicker",
- certificate: "platform",
product_specific: true,
}
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlend.java b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
index fdcd61b..54c8907 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlend.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
@@ -99,7 +99,7 @@
* @param opt LaunchOptions for clipping
*/
public void forEachSrc(Allocation ain, Allocation aout, Script.LaunchOptions opt) {
- blend(1, ain, aout, null);
+ blend(1, ain, aout, opt);
}
/**
@@ -636,4 +636,3 @@
}
*/
}
-
diff --git a/services/api/Android.bp b/services/api/Android.bp
index e69de29..bbc8c72 100644
--- a/services/api/Android.bp
+++ b/services/api/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 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 {
+ default_visibility: ["//visibility:private"],
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "non-updatable-system-server-current.txt",
+ srcs: ["non-updatable-current.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-server-removed.txt",
+ srcs: ["non-updatable-removed.txt"],
+ visibility: ["//frameworks/base/api"],
+}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index e9a099a..2c50389 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.Slog;
@@ -179,7 +180,8 @@
Context ctx = getContext();
if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED
|| mServiceNameResolver.isTemporary(userId)
- || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())
+ || Binder.getCallingUid() == Process.SYSTEM_UID)) {
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 735f420..cd332a6 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -40,6 +40,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.infra.AbstractRemoteService;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -55,6 +56,8 @@
private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
"predict_using_people_service_";
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
+
@Nullable
@GuardedBy("mLock")
@@ -112,8 +115,16 @@
@GuardedBy("mLock")
public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
- final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+ if (context.getExtras() != null
+ && context.getExtras().getBoolean(REMOTE_APP_PREDICTOR_KEY, false)
+ && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED, false)
+ ) {
+ // connect with remote AppPredictionService instead for dark launch
+ usesPeopleService = false;
+ }
final boolean serviceExists = resolveService(sessionId, false,
usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a3a0cb4..ab76b4b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -132,12 +132,15 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
@@ -170,6 +173,11 @@
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
+ private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ static {
+ sDateFormat.setTimeZone(TimeZone.getDefault());
+ }
+
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private PowerWhitelistManager mPowerWhitelistManager;
@@ -614,12 +622,14 @@
association.getDeviceMacAddress(),
association.getPackageName(),
association.getDeviceProfile(),
- active, /* notifyOnDeviceNearby */
+ active /* notifyOnDeviceNearby */,
association.getTimeApprovedMs());
} else {
return association;
}
}));
+
+ restartBleScan();
}
private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
@@ -675,12 +685,40 @@
synchronized (mLock) {
for (UserInfo user : getAllUsers()) {
forEach(mCachedAssociations.get(user.id), a -> {
- fout.append(" ")
- .append("u").append("" + a.getUserId()).append(": ")
- .append(a.getPackageName()).append(" - ")
- .append(a.getDeviceMacAddress()).append('\n');
+ fout.append(" ").append(a.toString()).append('\n');
});
}
+
+ }
+ fout.append("Currently Connected Devices:").append('\n');
+ for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) {
+ fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n');
+ }
+
+ fout.append("Devices Last Nearby:").append('\n');
+ for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) {
+ String device = mDevicesLastNearby.keyAt(i);
+ Date time = mDevicesLastNearby.valueAt(i);
+ fout.append(" ").append(device).append(" -> ")
+ .append(sDateFormat.format(time)).append('\n');
+ }
+
+ fout.append("Discovery Service State:").append('\n');
+ for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
+ int userId = mServiceConnectors.keyAt(i);
+ fout.append(" ")
+ .append("u").append(Integer.toString(userId)).append(": ")
+ .append(Objects.toString(mServiceConnectors.valueAt(i)))
+ .append('\n');
+ }
+
+ fout.append("Device Listener Services State:").append('\n');
+ for (int i = 0, size = mDeviceListenerServiceConnectors.size(); i < size; i++) {
+ int userId = mDeviceListenerServiceConnectors.keyAt(i);
+ fout.append(" ")
+ .append("u").append(Integer.toString(userId)).append(": ")
+ .append(Objects.toString(mDeviceListenerServiceConnectors.valueAt(i)))
+ .append('\n');
}
}
}
@@ -961,6 +999,7 @@
}
}
+
private Set<Association> getAllAssociations(
int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
return CollectionUtils.filter(
@@ -1242,7 +1281,8 @@
if (DEBUG) {
Slog.i(LOG_TAG, "Device " + address
+ " managed by " + association.getPackageName()
- + " disappeared; last seen on " + mDevicesLastNearby.get(address));
+ + " disappeared; last seen on "
+ + sDateFormat.format(mDevicesLastNearby.get(address)));
}
getDeviceListenerServiceConnector(association).run(
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index fdda239..9e126d7 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -43,6 +43,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderLatencyObserver;
import com.android.internal.os.CachedDeviceState;
import com.android.internal.util.DumpUtils;
@@ -124,6 +125,7 @@
/** Listens for flag changes. */
private static class SettingsObserver extends ContentObserver {
+ // Settings for BinderCallsStats.
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_DETAILED_TRACKING_KEY = "detailed_tracking";
private static final String SETTINGS_UPLOAD_DATA_KEY = "upload_data";
@@ -132,6 +134,10 @@
private static final String SETTINGS_TRACK_DIRECT_CALLING_UID_KEY = "track_calling_uid";
private static final String SETTINGS_MAX_CALL_STATS_KEY = "max_call_stats_count";
private static final String SETTINGS_IGNORE_BATTERY_STATUS_KEY = "ignore_battery_status";
+ // Settings for BinderLatencyObserver.
+ private static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_Latency_data";
+ private static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
+ "latency_observer_sampling_interval";
private boolean mEnabled;
private final Uri mUri = Settings.Global.getUriFor(Settings.Global.BINDER_CALLS_STATS);
@@ -188,6 +194,13 @@
mBinderCallsStats.setIgnoreBatteryStatus(
mParser.getBoolean(SETTINGS_IGNORE_BATTERY_STATUS_KEY,
BinderCallsStats.DEFAULT_IGNORE_BATTERY_STATUS));
+ mBinderCallsStats.setCollectLatencyData(
+ mParser.getBoolean(SETTINGS_COLLECT_LATENCY_DATA_KEY,
+ BinderCallsStats.DEFAULT_COLLECT_LATENCY_DATA));
+ // Binder latency observer settings.
+ mBinderCallsStats.getLatencyObserver().setSamplingInterval(mParser.getInt(
+ SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY,
+ BinderLatencyObserver.PERIODIC_SAMPLING_INTERVAL_DEFAULT));
final boolean enabled =
@@ -206,6 +219,7 @@
mEnabled = enabled;
mBinderCallsStats.reset();
mBinderCallsStats.setAddDebugEntries(enabled);
+ mBinderCallsStats.getLatencyObserver().reset();
}
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 27b648e..a559880 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3375,40 +3375,7 @@
}
}
- @Override
- public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) {
- enforceExternalStorageService();
-
- mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
- }
-
- @Override
- public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) {
- enforceExternalStorageService();
-
- mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
- }
-
- private boolean isAppIoBlocked(int uid) {
- return mStorageSessionController.isAppIoBlocked(uid);
- }
-
- /**
- * Enforces that the caller is the {@link ExternalStorageService}
- *
- * @throws SecurityException if the caller doesn't have the
- * {@link android.Manifest.permission.WRITE_MEDIA_STORAGE} permission or is not the
- * {@link ExternalStorageService}
- */
- private void enforceExternalStorageService() {
- enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
- int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
- if (callingAppId != mMediaStoreAuthorityAppId) {
- throw new SecurityException("Only the ExternalStorageService is permitted");
- }
- }
-
- /**
+ /*
* Returns PendingIntent which can be used by Apps with MANAGE_EXTERNAL_STORAGE permission
* to launch the manageSpaceActivity of the App specified by packageName.
*/
@@ -3455,6 +3422,39 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) {
+ enforceExternalStorageService();
+
+ mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason);
+ }
+
+ @Override
+ public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) {
+ enforceExternalStorageService();
+
+ mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason);
+ }
+
+ private boolean isAppIoBlocked(int uid) {
+ return mStorageSessionController.isAppIoBlocked(uid);
+ }
+
+ /**
+ * Enforces that the caller is the {@link ExternalStorageService}
+ *
+ * @throws SecurityException if the caller doesn't have the
+ * {@link android.Manifest.permission.WRITE_MEDIA_STORAGE} permission or is not the
+ * {@link ExternalStorageService}
+ */
+ private void enforceExternalStorageService() {
+ enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ if (callingAppId != mMediaStoreAuthorityAppId) {
+ throw new SecurityException("Only the ExternalStorageService is permitted");
+ }
+ }
/** Not thread safe */
class AppFuseMountScope extends AppFuseBridge.MountScope {
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 95af842..a09dbc7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -21,7 +21,9 @@
}
],
"file_patterns": ["NotificationManagerService\\.java"]
- },
+ }
+ ],
+ "presubmit-large": [
{
"name": "CtsScopedStorageCoreHostTest",
"file_patterns": ["StorageManagerService\\.java"]
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 277cb8c..0e68483 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -161,6 +161,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@@ -560,6 +561,45 @@
return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false;
}
+ boolean hasForegroundServiceNotificationLocked(String pkg, int userId, String channelId) {
+ final ServiceMap smap = mServiceMap.get(userId);
+ if (smap != null) {
+ for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+ final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+ if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
+ if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG_SERVICE, "Channel u" + userId + "/pkg=" + pkg
+ + "/channelId=" + channelId
+ + " has fg service notification");
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ void stopForegroundServicesForChannelLocked(String pkg, int userId, String channelId) {
+ final ServiceMap smap = mServiceMap.get(userId);
+ if (smap != null) {
+ for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) {
+ final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+ if (sr.appInfo.packageName.equals(pkg) && sr.isForeground) {
+ if (Objects.equals(sr.foregroundNoti.getChannelId(), channelId)) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG_SERVICE, "Stopping FGS u" + userId + "/pkg=" + pkg
+ + "/channelId=" + channelId
+ + " for conversation channel clear");
+ }
+ stopServiceLocked(sr, false);
+ }
+ }
+ }
+ }
+ }
+
private ServiceMap getServiceMapLocked(int callingUser) {
ServiceMap smap = mServiceMap.get(callingUser);
if (smap == null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f409d71..3c33276 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15880,6 +15880,22 @@
}
@Override
+ public boolean hasForegroundServiceNotification(String pkg, int userId,
+ String channelId) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.hasForegroundServiceNotificationLocked(pkg, userId, channelId);
+ }
+ }
+
+ @Override
+ public void stopForegroundServicesForChannel(String pkg, int userId,
+ String channelId) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServicesForChannelLocked(pkg, userId, channelId);
+ }
+ }
+
+ @Override
public void registerProcessObserver(IProcessObserver processObserver) {
ActivityManagerService.this.registerProcessObserver(processObserver);
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index e022e97..52ab4c8 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -92,6 +92,7 @@
DeviceConfig.NAMESPACE_STATSD_NATIVE,
DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT,
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+ DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
};
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f5b9417..8363c9d2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6958,7 +6958,10 @@
private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) {
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
- final int index = update.getVolumeIndex();
+ int index = update.getVolumeIndex();
+ if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+ index = safeMediaVolumeIndex(update.mDevice);
+ }
streamState.setIndex(index, update.mDevice, update.mCaller,
// trusted as index is always validated before message is posted
true /*hasModifyAudioSettings*/);
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 88b2668..aa9bbf6 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -162,7 +162,8 @@
private final ReduceBrightColorsTintController mReduceBrightColorsTintController =
new ReduceBrightColorsTintController();
- private final Handler mHandler;
+ @VisibleForTesting
+ final Handler mHandler;
private final AppSaturationController mAppSaturationController = new AppSaturationController();
@@ -404,13 +405,13 @@
// existing activated state. This ensures consistency of tint across the color mode change.
onDisplayColorModeChanged(getColorModeInternal());
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
if (mNightDisplayTintController.isAvailable(getContext())) {
// Reset the activated state.
mNightDisplayTintController.setActivated(null);
// Prepare the night display color transformation matrix.
- mNightDisplayTintController
- .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
+ mNightDisplayTintController.setUp(getContext(), dtm.needsLinearColorMatrix());
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
@@ -432,8 +433,7 @@
}
if (mReduceBrightColorsTintController.isAvailable(getContext())) {
- mReduceBrightColorsTintController
- .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
+ mReduceBrightColorsTintController.setUp(getContext(), dtm.needsLinearColorMatrix());
onReduceBrightColorsStrengthLevelChanged();
final boolean reset = resetReduceBrightColors();
if (!reset) {
@@ -540,8 +540,8 @@
mDisplayWhiteBalanceTintController.cancelAnimator();
if (mNightDisplayTintController.isAvailable(getContext())) {
- mNightDisplayTintController
- .setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ mNightDisplayTintController.setUp(getContext(), dtm.needsLinearColorMatrix(mode));
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
}
@@ -731,10 +731,11 @@
@VisibleForTesting
void updateDisplayWhiteBalanceStatus() {
boolean oldActivated = mDisplayWhiteBalanceTintController.isActivated();
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
mDisplayWhiteBalanceTintController.setActivated(isDisplayWhiteBalanceSettingEnabled()
&& !mNightDisplayTintController.isActivated()
&& !isAccessibilityEnabled()
- && DisplayTransformManager.needsLinearColorMatrix());
+ && dtm.needsLinearColorMatrix());
boolean activated = mDisplayWhiteBalanceTintController.isActivated();
if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index 5c68c51..0dba9e1 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -239,7 +239,7 @@
/**
* Return true when the color matrix works in linear space.
*/
- public static boolean needsLinearColorMatrix() {
+ public boolean needsLinearColorMatrix() {
return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR,
DISPLAY_COLOR_UNMANAGED) != DISPLAY_COLOR_UNMANAGED;
}
@@ -247,7 +247,7 @@
/**
* Return true when the specified colorMode requires the color matrix to work in linear space.
*/
- public static boolean needsLinearColorMatrix(int colorMode) {
+ public boolean needsLinearColorMatrix(int colorMode) {
return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
}
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 8405bbe..d422d51 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -88,7 +88,7 @@
tv.updateActiveSource(current, "ActiveSourceHandler");
invokeCallback(HdmiControlManager.RESULT_SUCCESS);
} else {
- tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, true,
+ tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress,
mCallback);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 18f7a06865..ab002f6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -370,12 +370,11 @@
return;
}
int newPath = mService.portIdToPath(portId);
- startRoutingControl(oldPath, newPath, true, callback);
+ startRoutingControl(oldPath, newPath, callback);
}
@ServiceThreadOnly
- void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
- IHdmiControlCallback callback) {
+ void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
assertRunOnServiceThread();
if (oldPath == newPath) {
return;
@@ -385,7 +384,7 @@
mService.sendCecCommand(routingChange);
removeAction(RoutingControlAction.class);
addAndStartAction(
- new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
+ new RoutingControlAction(this, newPath, callback));
}
@ServiceThreadOnly
@@ -554,7 +553,7 @@
if (isTailOfActivePath(path, getActivePath())) {
int newPath = mService.portIdToPath(getActivePortId());
setActivePath(newPath);
- startRoutingControl(getActivePath(), newPath, false, null);
+ startRoutingControl(getActivePath(), newPath, null);
return true;
}
return false;
@@ -598,7 +597,7 @@
getActiveSource().invalidate();
removeAction(RoutingControlAction.class);
int newPath = HdmiUtils.twoBytesToInt(params, 2);
- addAndStartAction(new RoutingControlAction(this, newPath, true, null));
+ addAndStartAction(new RoutingControlAction(this, newPath, null));
}
return true;
}
@@ -1154,7 +1153,7 @@
// Seq #23
if (isTailOfActivePath(path, getActivePath())) {
int newPath = mService.portIdToPath(getActivePortId());
- startRoutingControl(getActivePath(), newPath, true, null);
+ startRoutingControl(getActivePath(), newPath, null);
}
}
@@ -1172,7 +1171,7 @@
if (!routingForBootup && !isProhibitMode()) {
int newPath = mService.portIdToPath(getActivePortId());
setActivePath(newPath);
- startRoutingControl(getActivePath(), newPath, routingForBootup, null);
+ startRoutingControl(getActivePath(), newPath, null);
}
} else {
int activePath = mService.getPhysicalAddress();
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index 6c147ed..5edd35a 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -18,12 +18,11 @@
import android.annotation.Nullable;
import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Feature action for routing control. Exchanges routing-related commands with other devices
@@ -43,23 +42,12 @@
// State in which we wait for <Routing Information> to arrive. If timed out, we use the
// latest routing path to set the new active source.
- private static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
-
- // State in which we wait for <Report Power Status> in response to <Give Device Power Status>
- // we have sent. If the response tells us the device power is on, we send <Set Stream Path>
- // to make it the active source. Otherwise we do not send <Set Stream Path>, and possibly
- // just show the blank screen.
- private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2;
+ @VisibleForTesting
+ static final int STATE_WAIT_FOR_ROUTING_INFORMATION = 1;
// Time out in millseconds used for <Routing Information>
private static final int TIMEOUT_ROUTING_INFORMATION_MS = 1000;
- // Time out in milliseconds used for <Report Power Status>
- private static final int TIMEOUT_REPORT_POWER_STATUS_MS = 1000;
-
- // true if <Give Power Status> should be sent once the new active routing path is determined.
- private final boolean mQueryDevicePowerStatus;
-
// If set to true, call {@link HdmiControlService#invokeInputChangeListener()} when
// the routing control/active source change happens. The listener should be called if
// the events are triggered by external events such as manual switch port change or incoming
@@ -71,12 +59,10 @@
// The latest routing path. Updated by each <Routing Information> from CEC switches.
private int mCurrentRoutingPath;
- RoutingControlAction(HdmiCecLocalDevice localDevice, int path, boolean queryDevicePowerStatus,
- IHdmiControlCallback callback) {
+ RoutingControlAction(HdmiCecLocalDevice localDevice, int path, IHdmiControlCallback callback) {
super(localDevice);
mCallback = callback;
mCurrentRoutingPath = path;
- mQueryDevicePowerStatus = queryDevicePowerStatus;
// Callback is non-null when routing control action is brought up by binder API. Use
// this as an indicator for the input change notification. These API calls will get
// the result through this callback, not through notification. Any other events that
@@ -109,39 +95,16 @@
removeActionExcept(RoutingControlAction.class, this);
addTimer(mState, TIMEOUT_ROUTING_INFORMATION_MS);
return true;
- } else if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS
- && opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
- handleReportPowerStatus(cmd.getParams()[0]);
- return true;
}
return false;
}
- private void handleReportPowerStatus(int devicePowerStatus) {
- if (isPowerOnOrTransient(getTvPowerStatus())) {
- updateActiveInput();
- if (isPowerOnOrTransient(devicePowerStatus)) {
- sendSetStreamPath();
- }
- }
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
-
private void updateActiveInput() {
HdmiCecLocalDeviceTv tv = tv();
tv.setPrevPortId(tv.getActivePortId());
tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
}
- private int getTvPowerStatus() {
- return tv().getPowerStatus();
- }
-
- private static boolean isPowerOnOrTransient(int status) {
- return status == HdmiControlManager.POWER_STATUS_ON
- || status == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
- }
-
private void sendSetStreamPath() {
sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(getSourceAddress(),
mCurrentRoutingPath));
@@ -160,46 +123,13 @@
}
switch (timeoutState) {
case STATE_WAIT_FOR_ROUTING_INFORMATION:
- HdmiDeviceInfo device =
- localDevice().mService.getHdmiCecNetwork().getDeviceInfoByPath(
- mCurrentRoutingPath);
- if (device != null && mQueryDevicePowerStatus) {
- int deviceLogicalAddress = device.getLogicalAddress();
- queryDevicePowerStatus(deviceLogicalAddress, new SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- handlDevicePowerStatusAckResult(
- error == HdmiControlManager.RESULT_SUCCESS);
- }
- });
- } else {
- updateActiveInput();
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
- return;
- case STATE_WAIT_FOR_REPORT_POWER_STATUS:
- if (isPowerOnOrTransient(getTvPowerStatus())) {
- updateActiveInput();
- sendSetStreamPath();
- }
+ updateActiveInput();
+ sendSetStreamPath();
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
return;
- }
- }
-
- private void queryDevicePowerStatus(int address, SendMessageCallback callback) {
- sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(), address),
- callback);
- }
-
- private void handlDevicePowerStatusAckResult(boolean acked) {
- if (acked) {
- mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
- addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
- } else {
- updateActiveInput();
- sendSetStreamPath();
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ default:
+ Slog.e("CEC", "Invalid timeoutState (" + timeoutState + ").");
+ return;
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 73db705..a4bc79b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2154,14 +2154,16 @@
mUserProfiles.updateCache(getContext());
- telephonyManager.listen(new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (mCallState == state) return;
- if (DBG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
- mCallState = state;
- }
- }, PhoneStateListener.LISTEN_CALL_STATE);
+ if (mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ telephonyManager.listen(new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ if (mCallState == state) return;
+ if (DBG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
+ mCallState = state;
+ }
+ }, PhoneStateListener.LISTEN_CALL_STATE);
+ }
mSettingsObserver = new SettingsObserver(mHandler);
@@ -3565,15 +3567,30 @@
pkg, uid, channelId, conversationId, true, includeDeleted);
}
+ // Returns 'true' if the given channel has a notification associated
+ // with an active foreground service.
+ private void enforceDeletingChannelHasNoFgService(String pkg, int userId,
+ String channelId) {
+ if (mAmi.hasForegroundServiceNotification(pkg, userId, channelId)) {
+ Slog.w(TAG, "Package u" + userId + "/" + pkg
+ + " may not delete notification channel '"
+ + channelId + "' with fg service");
+ throw new SecurityException("Not allowed to delete channel " + channelId
+ + " with a foreground service");
+ }
+ }
+
@Override
public void deleteNotificationChannel(String pkg, String channelId) {
checkCallerIsSystemOrSameApp(pkg);
final int callingUid = Binder.getCallingUid();
+ final int callingUser = UserHandle.getUserId(callingUid);
if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
throw new IllegalArgumentException("Cannot delete default channel");
}
+ enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+ callingUser, REASON_CHANNEL_BANNED, null);
mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
@@ -3586,19 +3603,23 @@
public void deleteConversationNotificationChannels(String pkg, int uid,
String conversationId) {
checkCallerIsSystem();
- final int callingUid = Binder.getCallingUid();
List<NotificationChannel> channels =
mPreferencesHelper.getNotificationChannelsByConversationId(
pkg, uid, conversationId);
if (!channels.isEmpty()) {
+ // Preflight for fg service notifications in these channels: do nothing
+ // unless they're all eligible
+ final int appUserId = UserHandle.getUserId(uid);
for (NotificationChannel nc : channels) {
+ final String channelId = nc.getId();
+ mAmi.stopForegroundServicesForChannel(pkg, appUserId, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
- UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
- mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+ appUserId, REASON_CHANNEL_BANNED, null);
+ mPreferencesHelper.deleteNotificationChannel(pkg, uid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
- UserHandle.getUserHandleForUid(callingUid),
+ UserHandle.getUserHandleForUid(uid),
mPreferencesHelper.getNotificationChannel(
- pkg, callingUid, nc.getId(), true),
+ pkg, uid, channelId, true),
NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
}
handleSavePolicyFile();
@@ -3629,13 +3650,20 @@
NotificationChannelGroup groupToDelete =
mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
if (groupToDelete != null) {
+ // Preflight for allowability
+ final int userId = UserHandle.getUserId(callingUid);
+ List<NotificationChannel> groupChannels = groupToDelete.getChannels();
+ for (int i = 0; i < groupChannels.size(); i++) {
+ enforceDeletingChannelHasNoFgService(pkg, userId,
+ groupChannels.get(i).getId());
+ }
List<NotificationChannel> deletedChannels =
mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+ userId, REASON_CHANNEL_BANNED,
null);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ec7b451..b596dea 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2910,6 +2910,17 @@
mReadMessages.append("Error reading: " + e.toString());
PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
+ } finally {
+ if (!mVersion.containsKey(StorageManager.UUID_PRIVATE_INTERNAL)) {
+ Slog.wtf(PackageManagerService.TAG,
+ "No internal VersionInfo found in settings, using current.");
+ findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
+ }
+ if (!mVersion.containsKey(StorageManager.UUID_PRIMARY_PHYSICAL)) {
+ Slog.wtf(PackageManagerService.TAG,
+ "No external VersionInfo found in settings, using current.");
+ findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
+ }
}
// If the build is setup to drop runtime permissions
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index c0c9e6d..8e4a182 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -47,7 +47,7 @@
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
@NonNull LocationTimeZoneProviderProxy proxy) {
- super(threadingDomain, providerName);
+ super(threadingDomain, providerName, new ZoneInfoDbTimeZoneIdValidator());
mProxy = Objects.requireNonNull(proxy);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 0d1692a..5883821 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -520,6 +520,12 @@
}
}
+ static void infoLog(String msg) {
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Slog.i(TAG, msg);
+ }
+ }
+
static void warnLog(String msg) {
warnLog(msg, null);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index ef2f357..b97c838 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -20,6 +20,7 @@
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
@@ -85,6 +86,18 @@
}
/**
+ * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood
+ * by the platform.
+ */
+ interface TimeZoneIdValidator {
+
+ /**
+ * Returns whether {@code timeZoneId} is supported by the platform or not.
+ */
+ boolean isValid(@NonNull String timeZoneId);
+ }
+
+ /**
* Information about the provider's current state.
*/
static class ProviderState {
@@ -364,13 +377,17 @@
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
+ @NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
+
/** Creates the instance. */
LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
- @NonNull String providerName) {
+ @NonNull String providerName,
+ @NonNull TimeZoneIdValidator timeZoneIdValidator) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
+ mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator);
}
/**
@@ -610,6 +627,25 @@
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(timeZoneProviderEvent);
+ // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
+ // the device's time zone. This logic prevents bad time zone IDs entering the time zone
+ // detection logic from third party code.
+ //
+ // An event containing an unknown time zone ID could occur if the provider is using a
+ // different TZDB version than the device. Provider developers are expected to take steps to
+ // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
+ // rules, or providing IDs based on the device's TZDB version, so this is not considered a
+ // common case.
+ //
+ // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
+ // enables immediate failover to a secondary provider, one that might provide valid IDs for
+ // the same location, which should provide better behavior than just ignoring the event.
+ if (hasInvalidTimeZones(timeZoneProviderEvent)) {
+ infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. "
+ + "Replacing it with uncertain event.");
+ timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent();
+ }
+
synchronized (mSharedLock) {
debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName
+ ", timeZoneProviderEvent=" + timeZoneProviderEvent);
@@ -707,6 +743,20 @@
}
}
+ private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) {
+ if (event.getSuggestion() == null) {
+ return false;
+ }
+
+ for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
+ if (!mTimeZoneIdValidator.isValid(timeZone)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@GuardedBy("mSharedLock")
private void assertIsStarted() {
ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
new file mode 100644
index 0000000..cab5ad2
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.timezonedetector.location;
+
+import android.annotation.NonNull;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+class ZoneInfoDbTimeZoneIdValidator implements
+ LocationTimeZoneProvider.TimeZoneIdValidator {
+
+ @Override
+ public boolean isValid(@NonNull String timeZoneId) {
+ return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
+ }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 5723e1d..ee30fa2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -50,8 +50,6 @@
*/
private final int mProcessId;
- private boolean mIsForeground;
-
/**
* All the clients that share the same resource would be under the same group id.
*
@@ -90,6 +88,12 @@
private int mUsingCiCamId = INVALID_RESOURCE_ID;
/**
+ * If the priority is overwritten through
+ * {@link TunerResourceManagerService#setPriority(int, int)}.
+ */
+ private boolean mIsPriorityOverwritten = false;
+
+ /**
* Optional arbitrary priority value given by the client.
*
* <p>This value can override the default priorotiy calculated from
@@ -121,17 +125,10 @@
}
/**
- * Set the current isForeground status.
+ * If the client priority is overwrttien.
*/
- public void setForeground(boolean isForeground) {
- mIsForeground = isForeground;
- }
-
- /**
- * Get the previous recorded isForeground status.
- */
- public boolean isForeground() {
- return mIsForeground;
+ public boolean isPriorityOverwritten() {
+ return mIsPriorityOverwritten;
}
public int getGroupId() {
@@ -153,6 +150,17 @@
mPriority = priority;
}
+ /**
+ * Overwrite the client priority.
+ */
+ public void overwritePriority(int priority) {
+ if (priority < 0) {
+ return;
+ }
+ mIsPriorityOverwritten = true;
+ mPriority = priority;
+ }
+
public void setNiceValue(int niceValue) {
mNiceValue = niceValue;
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 988582d..0c04b07 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -507,9 +507,8 @@
.useCase(profile.useCase)
.processId(pid)
.build();
- clientProfile.setForeground(checkIsForeground(pid));
clientProfile.setPriority(
- getClientPriority(profile.useCase, clientProfile.isForeground()));
+ getClientPriority(profile.useCase, checkIsForeground(pid)));
addClientProfile(clientId[0], clientProfile, listener);
}
@@ -547,8 +546,7 @@
return false;
}
- profile.setForeground(checkIsForeground(profile.getProcessId()));
- profile.setPriority(priority);
+ profile.overwritePriority(priority);
profile.setNiceValue(niceValue);
return true;
@@ -694,7 +692,7 @@
} else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
// Record the frontend id with the lowest client priority among all the
// in use frontends when no available frontend has been found.
- int priority = getOwnerClientPriority(fr.getOwnerClientId());
+ int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
@@ -760,7 +758,7 @@
} else {
// Record the lnb id with the lowest client priority among all the
// in use lnb when no available lnb has been found.
- int priority = getOwnerClientPriority(lnb.getOwnerClientId());
+ int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityLnbHandle = lnb.getHandle();
currentLowestPriority = priority;
@@ -818,7 +816,7 @@
}
for (int ownerId : cas.getOwnerClientIds()) {
// Record the client id with lowest priority that is using the current Cas system.
- int priority = getOwnerClientPriority(ownerId);
+ int priority = updateAndGetOwnerClientPriority(ownerId);
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
@@ -867,7 +865,7 @@
}
for (int ownerId : ciCam.getOwnerClientIds()) {
// Record the client id with lowest priority that is using the current Cas system.
- int priority = getOwnerClientPriority(ownerId);
+ int priority = updateAndGetOwnerClientPriority(ownerId);
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
@@ -966,18 +964,17 @@
}
@VisibleForTesting
- // This mothod is to sync up the request client's foreground/background status and update
- // the client priority accordingly whenever new resource request comes in.
- protected void clientPriorityUpdateOnRequest(ClientProfile requestProfile) {
- int pid = requestProfile.getProcessId();
- boolean currentIsForeground = checkIsForeground(pid);
- if (requestProfile.isForeground() == currentIsForeground) {
+ // This mothod is to sync up the request/holder client's foreground/background status and update
+ // the client priority accordingly whenever a new resource request comes in.
+ protected void clientPriorityUpdateOnRequest(ClientProfile profile) {
+ if (profile.isPriorityOverwritten()) {
// To avoid overriding the priority set through updateClientPriority API.
return;
}
- requestProfile.setForeground(currentIsForeground);
- requestProfile.setPriority(
- getClientPriority(requestProfile.getUseCase(), currentIsForeground));
+ int pid = profile.getProcessId();
+ boolean currentIsForeground = checkIsForeground(pid);
+ profile.setPriority(
+ getClientPriority(profile.getUseCase(), currentIsForeground));
}
@VisibleForTesting
@@ -1154,13 +1151,15 @@
}
/**
- * Get the owner client's priority.
+ * Update and get the owner client's priority.
*
* @param clientId the owner client id.
* @return the priority of the owner client.
*/
- private int getOwnerClientPriority(int clientId) {
- return getClientProfile(clientId).getPriority();
+ private int updateAndGetOwnerClientPriority(int clientId) {
+ ClientProfile profile = getClientProfile(clientId);
+ clientPriorityUpdateOnRequest(profile);
+ return profile.getPriority();
}
@VisibleForTesting
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 04af5c9..7d2bafd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8730,6 +8730,9 @@
return null;
}
final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ if (supervisorComponent == null) {
+ return null;
+ }
if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
poComponent)) {
return supervisorComponent;
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index e7d0121..eab3b77 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -388,9 +388,11 @@
private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
@Override
- public void onCreatePredictionSession(AppPredictionContext context,
+ public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
AppPredictionSessionId sessionId) {
- mSessions.put(sessionId, new SessionInfo(context, mDataManager, sessionId.getUserId()));
+ mSessions.put(sessionId,
+ new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+ getContext()));
}
@Override
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
index 28612f1..d256d9c 100644
--- a/services/people/java/com/android/server/people/SessionInfo.java
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -20,6 +20,7 @@
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppTarget;
import android.app.prediction.IPredictionCallback;
+import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -40,9 +41,9 @@
new RemoteCallbackList<>();
SessionInfo(AppPredictionContext predictionContext, DataManager dataManager,
- @UserIdInt int callingUserId) {
+ @UserIdInt int callingUserId, Context context) {
mAppTargetPredictor = AppTargetPredictor.create(predictionContext,
- this::updatePredictions, dataManager, callingUserId);
+ this::updatePredictions, dataManager, callingUserId, context);
}
void addCallback(IPredictionCallback callback) {
diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
index c89dadc..e191081 100644
--- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
@@ -24,6 +24,7 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.people.data.DataManager;
@@ -43,10 +44,10 @@
/** Creates a {@link AppTargetPredictor} instance based on the prediction context. */
public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId, Context context) {
if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) {
- return new ShareTargetPredictor(
- predictionContext, updatePredictionsMethod, dataManager, callingUserId);
+ return new ShareTargetPredictor(predictionContext, updatePredictionsMethod, dataManager,
+ callingUserId, context);
}
return new AppTargetPredictor(
predictionContext, updatePredictionsMethod, dataManager, callingUserId);
@@ -124,6 +125,11 @@
callback.accept(targets);
}
+ /** To be overridden by the subclass to recycle resources. */
+ @WorkerThread
+ void destroy() {
+ }
+
AppPredictionContext getPredictionContext() {
return mPredictionContext;
}
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 236ac84..368b737 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,6 +16,8 @@
package com.android.server.people.prediction;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
import static java.util.Collections.reverseOrder;
import android.annotation.NonNull;
@@ -23,17 +25,23 @@
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -52,14 +60,27 @@
private static final String TAG = "ShareTargetPredictor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
private final IntentFilter mIntentFilter;
+ private final AppPredictor mRemoteAppPredictor;
ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager,
+ @UserIdInt int callingUserId, @NonNull Context context) {
super(predictionContext, updatePredictionsMethod, dataManager, callingUserId);
mIntentFilter = predictionContext.getExtras().getParcelable(
ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ false)) {
+ predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true);
+ mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0)
+ .getSystemService(AppPredictionManager.class)
+ .createAppPredictionSession(predictionContext);
+ } else {
+ mRemoteAppPredictor = null;
+ }
}
/** Reports chosen history of direct/app share targets. */
@@ -72,6 +93,9 @@
if (mIntentFilter != null) {
getDataManager().reportShareTargetEvent(event, mIntentFilter);
}
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.notifyAppTargetEvent(event);
+ }
}
/** Provides prediction on direct share targets */
@@ -129,6 +153,15 @@
callback.accept(appTargetList);
}
+ /** Recycles resources. */
+ @WorkerThread
+ @Override
+ void destroy() {
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.destroy();
+ }
+ }
+
private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index a19b387..363c26b 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -21,7 +21,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,9 +52,7 @@
import com.android.server.twilight.TwilightState;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -76,25 +73,29 @@
private int mUserId;
private MockTwilightManager mTwilightManager;
+ private DisplayTransformManager mDisplayTransformManager;
private ColorDisplayService mCds;
private ColorDisplayService.BinderService mBinderService;
private Resources mResourcesSpy;
- @BeforeClass
- public static void setDtm() {
- final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
- LocalServices.addService(DisplayTransformManager.class, dtm);
- }
+ private static final int[] MINIMAL_COLOR_MODES = new int[] {
+ ColorDisplayManager.COLOR_MODE_NATURAL,
+ ColorDisplayManager.COLOR_MODE_BOOSTED,
+ };
@Before
public void setUp() {
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
doReturn(mContext).when(mContext).getApplicationContext();
- mResourcesSpy = Mockito.spy(mContext.getResources());
- when(mContext.getResources()).thenReturn(mResourcesSpy);
+ final Resources res = Mockito.spy(mContext.getResources());
+ doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
+ doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
+ doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
+ when(mContext.getResources()).thenReturn(res);
+ mResourcesSpy = res;
mUserId = ActivityManager.getCurrentUser();
@@ -108,6 +109,10 @@
mTwilightManager = new MockTwilightManager();
LocalServices.addService(TwilightManager.class, mTwilightManager);
+ mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
+ doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
+ LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+
mCds = new ColorDisplayService(mContext);
mBinderService = mCds.new BinderService();
LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
@@ -116,12 +121,18 @@
@After
public void tearDown() {
- LocalServices.removeServiceForTest(TwilightManager.class);
-
+ /*
+ * Wait for internal {@link Handler} to finish processing pending messages, so that test
+ * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
+ */
+ mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
mCds = null;
+ LocalServices.removeServiceForTest(TwilightManager.class);
mTwilightManager = null;
+ LocalServices.removeServiceForTest(DisplayTransformManager.class);
+
mUserId = UserHandle.USER_NULL;
mContext = null;
@@ -130,11 +141,6 @@
LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
}
- @AfterClass
- public static void removeDtm() {
- LocalServices.removeServiceForTest(DisplayTransformManager.class);
- }
-
@Test
public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
@@ -1064,24 +1070,18 @@
@Test
public void compositionColorSpaces_noResources() {
- final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- reset(dtm);
-
when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
.thenReturn(new int[] {});
when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
.thenReturn(new int[] {});
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
- eq(Display.COLOR_MODE_INVALID));
+ verify(mDisplayTransformManager).setColorMode(
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
}
@Test
public void compositionColorSpaces_invalidResources() {
- final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- reset(dtm);
-
when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
.thenReturn(new int[] {
ColorDisplayManager.COLOR_MODE_NATURAL,
@@ -1094,15 +1094,12 @@
});
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
- eq(Display.COLOR_MODE_INVALID));
+ verify(mDisplayTransformManager).setColorMode(
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
}
@Test
public void compositionColorSpaces_validResources_validColorMode() {
- final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- reset(dtm);
-
when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
.thenReturn(new int[] {
ColorDisplayManager.COLOR_MODE_NATURAL
@@ -1113,15 +1110,12 @@
});
setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(),
- eq(Display.COLOR_MODE_SRGB));
+ verify(mDisplayTransformManager).setColorMode(
+ eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
}
@Test
public void compositionColorSpaces_validResources_invalidColorMode() {
- final DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class);
- reset(dtm);
-
when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
.thenReturn(new int[] {
ColorDisplayManager.COLOR_MODE_NATURAL
@@ -1132,8 +1126,8 @@
});
setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
startService();
- verify(dtm).setColorMode(eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(),
- eq(Display.COLOR_MODE_INVALID));
+ verify(mDisplayTransformManager).setColorMode(
+ eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
new file mode 100644
index 0000000..0ed6d7be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2014 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE;
+import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class RoutingControlActionTest {
+ /*
+ * Example connection diagram used in tests. Double-lined paths indicate the currently active
+ * routes.
+ *
+ *
+ * +-----------+
+ * | TV |
+ * | 0.0.0.0 |
+ * +---+-----+-+
+ * | |
+ * <----------+ 1) AVR -> Switch
+ * +----------+ | | +-----------+
+ * | AVR +---------+ +--+ Switch |
+ * | 1.0.0.0 | | 2.0.0.0 |
+ * +--+---++--+ +--++-----+-+ <-------+ 2) Recorder -> Blu-ray
+ * | || || |
+ * | || || +--------+
+ * +-----------+ | || +----------+ +----++----+ |
+ * | XBox +--+ ++--+ Tuner | | Blueray | +-----+----+
+ * | 1.1.0.0 | | 1.2.0.0 | | 2.1.0.0 | | Recorder |
+ * +-----------+ +----++----+ +----------+ | 2.2.0.0 |
+ * || +----------+
+ * ||
+ * +----++----+
+ * | Player |
+ * | 1.2.1.0 |
+ * +----------+
+ *
+ */
+
+ private static final int PHYSICAL_ADDRESS_TV = 0x0000;
+ private static final int PHYSICAL_ADDRESS_AVR = 0x1000;
+ private static final int PHYSICAL_ADDRESS_SWITCH = 0x2000;
+ private static final int PHYSICAL_ADDRESS_TUNER = 0x1200;
+ private static final int PHYSICAL_ADDRESS_PLAYER = 0x1210;
+ private static final int PHYSICAL_ADDRESS_BLUERAY = 0x2100;
+ private static final int PHYSICAL_ADDRESS_RECORDER = 0x2200;
+ private static final int PORT_1 = 1;
+ private static final int PORT_2 = 2;
+ private static final int VENDOR_ID_AVR = 0x11233;
+
+ private static final byte[] TUNER_PARAM =
+ new byte[] {(PHYSICAL_ADDRESS_TUNER >> 8) & 0xFF, PHYSICAL_ADDRESS_TUNER & 0xFF};
+ private static final byte[] PLAYER_PARAM =
+ new byte[] {(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF};
+
+ private static final HdmiDeviceInfo DEVICE_INFO_AVR =
+ new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1,
+ HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio");
+ private static final HdmiDeviceInfo DEVICE_INFO_PLAYER =
+ new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1,
+ HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player");
+ private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage(
+ ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM);
+ private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage(
+ ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM);
+ private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage(
+ ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM);
+ private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage(
+ ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM);
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
+ private FakeNativeWrapper mNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+ @Mock
+ private IPowerManager mIPowerManagerMock;
+ @Mock
+ private IThermalService mIThermalServiceMock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mMyLooper = mTestLooper.getLooper();
+ PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mMyLooper));
+
+ HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context);
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+
+ @Override
+ void wakeUp() {
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+
+ @Override
+ boolean isPowerStandbyOrTransient() {
+ return false;
+ }
+
+ @Override
+ protected PowerManager getPowerManager() {
+ return powerManager;
+ }
+
+ @Override
+ protected HdmiCecConfig getHdmiCecConfig() {
+ return hdmiCecConfig;
+ }
+ };
+
+ mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService);
+ mHdmiCecLocalDeviceTv.init();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+ mLocalDevices.add(mHdmiCecLocalDeviceTv);
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR,
+ true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mHdmiControlService.initService();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
+ }
+
+ private static class TestActionTimer implements ActionTimer {
+ private int mState;
+
+ @Override
+ public void sendTimerMessage(int state, long delayMillis) {
+ mState = state;
+ }
+
+ @Override
+ public void clearTimerMessage() {
+ }
+
+ private int getState() {
+ return mState;
+ }
+ }
+
+ private static class TestInputSelectCallback extends IHdmiControlCallback.Stub {
+ private final List<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assert (mCallbackResult.size() == 1);
+ return mCallbackResult.get(0);
+ }
+ }
+
+ private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice,
+ TestInputSelectCallback callback) {
+ return new RoutingControlAction(localDevice, PHYSICAL_ADDRESS_AVR, callback);
+ }
+
+ // Routing control succeeds against the device connected directly to the port. Action
+ // won't get any <Routing Information> in this case. It times out on <Routing Information>,
+ // regards the directly connected one as the new routing path to switch to.
+ @Test
+ public void testRoutingControl_succeedForDirectlyConnectedDevice() {
+ TestInputSelectCallback callback = new TestInputSelectCallback();
+ TestActionTimer actionTimer = new TestActionTimer();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
+
+ RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
+ action.setActionTimer(actionTimer);
+ action.start();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
+
+ action.handleTimerEvent(actionTimer.getState());
+ mTestLooper.dispatchAll();
+ HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+ ADDR_TV, PHYSICAL_ADDRESS_AVR);
+ assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+ }
+
+ // Succeeds by receiving a couple of <Routing Information> commands, followed by
+ // <Set Stream Path> going out in the end.
+ @Test
+ public void testRoutingControl_succeedForDeviceBehindSwitch() {
+ TestInputSelectCallback callback = new TestInputSelectCallback();
+ TestActionTimer actionTimer = new TestActionTimer();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_PLAYER);
+ RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
+ action.setActionTimer(actionTimer);
+ action.start();
+
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
+
+ action.processCommand(ROUTING_INFORMATION_TUNER);
+ action.processCommand(ROUTING_INFORMATION_PLAYER);
+
+ action.handleTimerEvent(actionTimer.getState());
+ mTestLooper.dispatchAll();
+ HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
+ ADDR_TV, PHYSICAL_ADDRESS_PLAYER);
+ assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index ecff409..5066240 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -47,12 +47,14 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import org.junit.After;
@@ -126,6 +128,10 @@
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
.setExtras(new Bundle())
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(false),
+ true /* makeDefault*/);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index b09a3c3..fac5c1f 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -22,13 +22,17 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
@@ -38,9 +42,11 @@
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Range;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -71,6 +77,14 @@
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
+ private static final AppTargetEvent APP_TARGET_EVENT =
+ new AppTargetEvent.Builder(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)).build(),
+ AppTargetEvent.ACTION_LAUNCH)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
+ .build();
+ private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
@Mock private Context mContext;
@Mock private DataManager mDataManager;
@@ -102,17 +116,33 @@
when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
+ when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+ when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
+ Context.APP_PREDICTION_SERVICE);
+ when(mContext.getSystemService(AppPredictionManager.class))
+ .thenReturn(new AppPredictionManager(mContext));
Bundle bundle = new Bundle();
- bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY,
- IntentFilter.create("SEND", "text/plain"));
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
.setUiSurface(UI_SURFACE_SHARE)
.setPredictedTargetCount(NUM_PREDICTED_TARGETS)
.setExtras(bundle)
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(true),
+ true /* makeDefault*/);
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+ }
+
+ @Test
+ public void testReportAppTargetEvent() {
+ mPredictor.reportAppTargetEvent(APP_TARGET_EVENT);
+
+ verify(mDataManager, times(1))
+ .reportShareTargetEvent(eq(APP_TARGET_EVENT), eq(INTENT_FILTER));
}
@Test
@@ -240,7 +270,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
mPredictor.predictTargets();
@@ -349,7 +379,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
AppTarget appTarget1 = new AppTarget.Builder(
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.build();
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 4284240..3daa7f0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -72,6 +72,7 @@
private TestCallback mTestCallback;
private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
+ private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
@Before
public void setUp() {
@@ -80,10 +81,13 @@
// will never get a chance to execute.
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
+ mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
mTestPrimaryLocationTimeZoneProvider =
- new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain, "primary", mTimeZoneAvailabilityChecker);
mTestSecondaryLocationTimeZoneProvider =
- new TestLocationTimeZoneProvider(mTestThreadingDomain, "secondary");
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain, "secondary", mTimeZoneAvailabilityChecker);
}
@Test
@@ -1177,8 +1181,10 @@
/**
* Creates the instance.
*/
- TestLocationTimeZoneProvider(ThreadingDomain threadingDomain, String providerName) {
- super(threadingDomain, providerName);
+ TestLocationTimeZoneProvider(ThreadingDomain threadingDomain,
+ String providerName,
+ TimeZoneIdValidator timeZoneIdValidator) {
+ super(threadingDomain, providerName, timeZoneIdValidator);
}
public void setFailDuringInitialization(boolean failInitialization) {
@@ -1311,4 +1317,14 @@
mTestProviderState.commitLatest();
}
}
+
+ private static final class FakeTimeZoneIdValidator
+ implements LocationTimeZoneProvider.TimeZoneIdValidator {
+
+ @Override
+ public boolean isValid(@NonNull String timeZoneId) {
+ return true;
+ }
+
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 095c868..278fdaf 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -32,6 +32,8 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static java.util.Arrays.asList;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -50,7 +52,9 @@
import java.time.Duration;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -62,20 +66,25 @@
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L;
private TestThreadingDomain mTestThreadingDomain;
-
private TestProviderListener mProviderListener;
+ private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
@Before
public void setUp() {
mTestThreadingDomain = new TestThreadingDomain();
mProviderListener = new TestProviderListener();
+ mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
}
@Test
public void lifecycle() {
String providerName = "arbitrary";
TestLocationTimeZoneProvider provider =
- new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneAvailabilityChecker);
+ mTimeZoneAvailabilityChecker.validIds("Europe/London");
// initialize()
provider.initialize(mProviderListener);
@@ -163,7 +172,10 @@
public void defaultHandleTestCommandImpl() {
String providerName = "primary";
TestLocationTimeZoneProvider provider =
- new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneAvailabilityChecker);
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
AtomicReference<Bundle> resultReference = new AtomicReference<>();
@@ -180,8 +192,12 @@
public void stateRecording() {
String providerName = "primary";
TestLocationTimeZoneProvider provider =
- new TestLocationTimeZoneProvider(mTestThreadingDomain, providerName);
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneAvailabilityChecker);
provider.setStateChangeRecordingEnabled(true);
+ mTimeZoneAvailabilityChecker.validIds("Europe/London");
// initialize()
provider.initialize(mProviderListener);
@@ -218,6 +234,34 @@
provider.assertLatestRecordedState(PROVIDER_STATE_DESTROYED);
}
+ @Test
+ public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
+ String providerName = "primary";
+ TestLocationTimeZoneProvider provider =
+ new TestLocationTimeZoneProvider(
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneAvailabilityChecker);
+ provider.setStateChangeRecordingEnabled(true);
+ provider.initialize(mProviderListener);
+
+ ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
+ Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+ Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ provider.startUpdates(config, arbitraryInitializationTimeout,
+ arbitraryInitializationTimeoutFuzz);
+
+ List<String> invalidTimeZoneIds = asList("Atlantic/Atlantis");
+ TimeZoneProviderSuggestion invalidIdSuggestion = new TimeZoneProviderSuggestion.Builder()
+ .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+ .setTimeZoneIds(invalidTimeZoneIds)
+ .build();
+ TimeZoneProviderEvent event =
+ TimeZoneProviderEvent.createSuggestionEvent(invalidIdSuggestion);
+ provider.simulateProviderEventReceived(event);
+ provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
+ }
+
/** A test stand-in for the real {@link LocationTimeZoneProviderController}'s listener. */
private static class TestProviderListener implements ProviderListener {
@@ -251,8 +295,9 @@
/** Creates the instance. */
TestLocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
- @NonNull String providerName) {
- super(threadingDomain, providerName);
+ @NonNull String providerName,
+ @NonNull TimeZoneIdValidator timeZoneIdValidator) {
+ super(threadingDomain, providerName, timeZoneIdValidator);
}
@Override
@@ -308,4 +353,19 @@
recordedStates.get(recordedStates.size() - 1).stateEnum);
}
}
+
+ private static final class FakeTimeZoneIdValidator
+ implements LocationTimeZoneProvider.TimeZoneIdValidator {
+ private final Set<String> mValidTimeZoneIds = new HashSet<>();
+
+ @Override
+ public boolean isValid(@NonNull String timeZoneId) {
+ return mValidTimeZoneIds.contains(timeZoneId);
+ }
+
+ public void validIds(String... timeZoneIdss) {
+ mValidTimeZoneIds.addAll(asList(timeZoneIdss));
+ }
+
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
new file mode 100644
index 0000000..5561b2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 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.timezonedetector.location;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+@Presubmit
+public class ZoneInfoDbTimeZoneIdValidatorTest {
+ private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker =
+ new ZoneInfoDbTimeZoneIdValidator();
+
+ @Test
+ public void timeZoneIdsFromZoneInfoDbAreValid() {
+ for (String timeZone : TimeZone.getAvailableIDs()) {
+ assertWithMessage("Time zone %s should be supported", timeZone)
+ .that(mTzChecker.isValid(timeZone)).isTrue();
+ }
+ }
+
+ @Test
+ public void nonExistingZones_areNotSupported() {
+ List<String> nonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30"
+ );
+
+ for (String timeZone : nonExistingTimeZones) {
+ assertWithMessage(timeZone + " is not a valid time zone")
+ .that(mTzChecker.isValid(timeZone))
+ .isFalse();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index aadab6e..2f36c7f 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -365,13 +365,13 @@
mTunerResourceManagerService.registerClientProfileInternal(
profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId0[0])
- .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId0[0], clientPriorities[0], 0/*niceValue*/);
mTunerResourceManagerService.registerClientProfileInternal(
profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId1[0])
- .setPriority(clientPriorities[1]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId1[0], clientPriorities[1], 0/*niceValue*/);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -415,13 +415,13 @@
mTunerResourceManagerService.registerClientProfileInternal(
profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId0[0])
- .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId0[0], clientPriorities[0], 0/*niceValue*/);
mTunerResourceManagerService.registerClientProfileInternal(
profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId1[0])
- .setPriority(clientPriorities[1]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId1[0], clientPriorities[1], 0/*niceValue*/);
// Init frontend resources.
TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -511,13 +511,13 @@
mTunerResourceManagerService.registerClientProfileInternal(
profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId0[0])
- .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId0[0], clientPriorities[0], 0/*niceValue*/);
mTunerResourceManagerService.registerClientProfileInternal(
profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId1[0])
- .setPriority(clientPriorities[1]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId1[0], clientPriorities[1], 0/*niceValue*/);
// Init cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
@@ -567,13 +567,13 @@
mTunerResourceManagerService.registerClientProfileInternal(
profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId0[0])
- .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId0[0], clientPriorities[0], 0/*niceValue*/);
mTunerResourceManagerService.registerClientProfileInternal(
profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId1[0])
- .setPriority(clientPriorities[1]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId1[0], clientPriorities[1], 0/*niceValue*/);
// Init cicam/cas resources.
mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
@@ -697,13 +697,13 @@
mTunerResourceManagerService.registerClientProfileInternal(
profiles[0], listener, clientId0);
assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId0[0])
- .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId0[0], clientPriorities[0], 0/*niceValue*/);
mTunerResourceManagerService.registerClientProfileInternal(
profiles[1], new TestResourcesReclaimListener(), clientId1);
assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
- mTunerResourceManagerService.getClientProfile(clientId1[0])
- .setPriority(clientPriorities[1]);
+ mTunerResourceManagerService.updateClientPriorityInternal(
+ clientId1[0], clientPriorities[1], 0/*niceValue*/);
// Init lnb resources.
int[] lnbHandles = {1};
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index a0a3909..61074ba 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -207,7 +207,7 @@
public boolean isReservedSupported(String volumeUuid, String callingPackage) {
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false)
- || Build.IS_CONTAINER;
+ || Build.IS_ARC;
} else {
return false;
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 4926687..0070f62 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2678,7 +2678,8 @@
* @throws IllegalArgumentException if contentUri is empty
*/
public void sendMultimediaMessage(@NonNull Context context, @NonNull Uri contentUri,
- @Nullable String locationUrl, @Nullable Bundle configOverrides,
+ @Nullable String locationUrl,
+ @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
@Nullable PendingIntent sentIntent, long messageId) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
@@ -2754,7 +2755,8 @@
* @throws IllegalArgumentException if locationUrl or contentUri is empty
*/
public void downloadMultimediaMessage(@NonNull Context context, @NonNull String locationUrl,
- @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+ @NonNull Uri contentUri,
+ @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
@Nullable PendingIntent downloadedIntent, long messageId) {
if (TextUtils.isEmpty(locationUrl)) {
throw new IllegalArgumentException("Empty MMS location URL");
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index cf4e677..5bbd061 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -602,6 +602,43 @@
public @interface SimDisplayNameSource {}
/**
+ * Device status is not shared to a remote party.
+ */
+ public static final int D2D_SHARING_DISABLED = 0;
+
+ /**
+ * Device status is shared with all numbers in the user's contacts.
+ */
+ public static final int D2D_SHARING_ALL_CONTACTS = 1;
+
+ /**
+ * Device status is shared with all starred contacts.
+ */
+ public static final int D2D_SHARING_STARRED_CONTACTS = 2;
+
+ /**
+ * Device status is shared whenever possible.
+ */
+ public static final int D2D_SHARING_ALL = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"D2D_SHARING_"},
+ value = {
+ D2D_SHARING_DISABLED,
+ D2D_SHARING_ALL_CONTACTS,
+ D2D_SHARING_STARRED_CONTACTS,
+ D2D_SHARING_ALL
+ })
+ public @interface DeviceToDeviceStatusSharing {}
+
+ /**
+ * TelephonyProvider column name for device to device sharing status.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING;
+
+ /**
* TelephonyProvider column name for the color of a SIM.
* <P>Type: INTEGER (int)</P>
*/
@@ -3374,6 +3411,36 @@
}
/**
+ * Set the device to device status sharing user preference for a subscription ID. The setting
+ * app uses this method to indicate with whom they wish to share device to device status
+ * information.
+ * @param sharing the status sharing preference
+ * @param subId the unique Subscription ID in database
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
+ int subId) {
+ if (VDBG) {
+ logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+ }
+ setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+ }
+
+ /**
+ * Returns the user-chosen device to device status sharing preference
+ * @param subId Subscription id of subscription
+ * @return The device to device status sharing preference
+ */
+ public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+ if (VDBG) {
+ logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+ }
+ return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
+ mContext);
+ }
+
+ /**
* DO NOT USE.
* This API is designed for features that are not finished at this point. Do not call this API.
* @hide
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 5b5570b..ed2b1a9 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -79,7 +79,7 @@
* <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
* <li>The device is connected to the specific network which the carrier is using
* proprietary means to provide a faster overall data connection than would be otherwise
- * possible. This may include using other bands unique to the carrier, or carrier
+ * possible. This may include using other bands unique to the carrier, or carrier
* aggregation, for example.</li>
* </ul>
* One of the use case is that UX can show a different icon, for example, "5G+"
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 571efce..9493c76 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -300,4 +300,6 @@
boolean canDisablePhysicalSubscription();
int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
+
+ int setDeviceToDeviceStatusSharing(int sharing, int subId);
}
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 2c6c8d7..1381c0a 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -73,6 +73,14 @@
android:text="@string/play_trigger"
android:onClick="onPlayTriggerButtonClicked"
android:padding="20dp" />
+
+ <Button
+ android:id="@+id/get_state_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/get_model_state"
+ android:onClick="onGetModelStateButtonClicked"
+ android:padding="20dp" />
</LinearLayout>
<LinearLayout
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index c48b648..adb0fcf 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -22,6 +22,7 @@
<string name="start_recog">Start</string>
<string name="stop_recog">Stop</string>
<string name="play_trigger">Play Trigger Audio</string>
+ <string name="get_model_state">Get State</string>
<string name="capture">Capture Audio</string>
<string name="stop_capture">Stop Capturing Audio</string>
<string name="play_capture">Play Captured Audio</string>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
index c3c4cf5..72aa38d 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
@@ -257,6 +257,14 @@
}
}
+ public synchronized void onGetModelStateButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't get model state: not bound to SoundTriggerTestService");
+ } else {
+ mService.getModelState(mSelectedModelUuid);
+ }
+ }
+
public synchronized void onCaptureAudioCheckboxClicked(View v) {
// See if we have the right permissions
if (!mService.hasMicrophonePermission()) {
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
index 380e299..6d4ffcf 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -46,6 +48,7 @@
import java.util.Random;
import java.util.UUID;
+
public class SoundTriggerTestService extends Service {
private static final String TAG = "SoundTriggerTestSrv";
private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER";
@@ -57,6 +60,8 @@
private Random mRandom;
private UserActivity mUserActivity;
+ private static int captureCount;
+
public interface UserActivity {
void addModel(UUID modelUuid, String state);
void setModelState(UUID modelUuid, String state);
@@ -131,6 +136,8 @@
} else if (command.equals("set_capture_timeout")) {
setCaptureAudioTimeout(getModelUuidFromIntent(intent),
intent.getIntExtra("timeout", 5000));
+ } else if (command.equals("get_model_state")) {
+ getModelState(getModelUuidFromIntent(intent));
} else {
Log.e(TAG, "Unknown command '" + command + "'");
}
@@ -432,6 +439,17 @@
return modelInfo != null && modelInfo.captureAudioTrack != null;
}
+ public synchronized void getModelState(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ int status = mSoundTriggerUtil.getModelState(modelUuid);
+ postMessage("GetModelState for: " + modelInfo.name + " returns: "
+ + status);
+ }
+
private void loadModelsInDataDir() {
// Load all the models in the data dir.
boolean loadedModel = false;
@@ -527,18 +545,29 @@
}
}
+
private class CaptureAudioRecorder implements Runnable {
private final ModelInfo mModelInfo;
+
+ // EventPayload and RecognitionEvent are equivalant. Only one will be non-null.
private final SoundTriggerDetector.EventPayload mEvent;
+ private final RecognitionEvent mRecognitionEvent;
public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) {
mModelInfo = modelInfo;
mEvent = event;
+ mRecognitionEvent = null;
+ }
+
+ public CaptureAudioRecorder(ModelInfo modelInfo, RecognitionEvent event) {
+ mModelInfo = modelInfo;
+ mEvent = null;
+ mRecognitionEvent = event;
}
@Override
public void run() {
- AudioFormat format = mEvent.getCaptureAudioFormat();
+ AudioFormat format = getAudioFormat();
if (format == null) {
postErrorToast("No audio format in recognition event.");
return;
@@ -600,18 +629,21 @@
}
audioRecord = new AudioRecord(attributes, format, bytesRequired,
- mEvent.getCaptureSession());
+ getCaptureSession());
byte[] buffer = new byte[bytesRequired];
// Create a file so we can save the output data there for analysis later.
FileOutputStream fos = null;
try {
- fos = new FileOutputStream( new File(
- getFilesDir() + File.separator
- + mModelInfo.name.replace(' ', '_')
- + "_capture_" + format.getChannelCount() + "ch_"
- + format.getSampleRate() + "hz_" + encoding + ".pcm"));
+ File file = new File(
+ getFilesDir() + File.separator
+ + mModelInfo.name.replace(' ', '_')
+ + "_capture_" + format.getChannelCount() + "ch_"
+ + format.getSampleRate() + "hz_" + encoding
+ + "_" + (++captureCount) + ".pcm");
+ Log.i(TAG, "Writing audio to: " + file);
+ fos = new FileOutputStream(file);
} catch (IOException e) {
Log.e(TAG, "Failed to open output for saving PCM data", e);
postErrorToast("Failed to open output for saving PCM data: "
@@ -635,6 +667,10 @@
bytesRequired -= bytesRead;
}
audioRecord.stop();
+ if (fos != null) {
+ fos.flush();
+ fos.close();
+ }
} catch (Exception e) {
Log.e(TAG, "Error recording trigger audio", e);
postErrorToast("Error recording trigger audio: " + e.getMessage());
@@ -651,6 +687,26 @@
setModelState(mModelInfo, "Recording finished");
}
}
+
+ private AudioFormat getAudioFormat() {
+ if (mEvent != null) {
+ return mEvent.getCaptureAudioFormat();
+ }
+ if (mRecognitionEvent != null) {
+ return mRecognitionEvent.captureFormat;
+ }
+ return null;
+ }
+
+ private int getCaptureSession() {
+ if (mEvent != null) {
+ return mEvent.getCaptureSession();
+ }
+ if (mRecognitionEvent != null) {
+ return mRecognitionEvent.captureSession;
+ }
+ return 0;
+ }
}
// Implementation of SoundTriggerDetector.Callback.
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index cfe8c85..996a78f 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
import android.os.RemoteException;
@@ -27,6 +29,7 @@
import com.android.internal.app.ISoundTriggerService;
+import java.lang.reflect.Method;
import java.lang.RuntimeException;
import java.util.UUID;
@@ -50,13 +53,31 @@
* The sound model must contain a valid UUID.
*
* @param soundModel The sound model to add/update.
+ * @return The true if the model was loaded successfully, false otherwise.
*/
public boolean addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
if (soundModel == null) {
throw new RuntimeException("Bad sound model");
}
mSoundTriggerManager.updateModel(soundModel);
- return true;
+ // TODO: call loadSoundModel in the soundtrigger manager updateModel method
+ // instead of here. It is needed to keep soundtrigger manager internal
+ // state consistent.
+ return mSoundTriggerManager
+ .loadSoundModel(getGenericSoundModel(soundModel)) == 0;
+ }
+
+ private GenericSoundModel getGenericSoundModel(
+ SoundTriggerManager.Model soundModel) {
+ try {
+ Method method = SoundTriggerManager.Model.class
+ .getDeclaredMethod("getGenericSoundModel");
+ method.setAccessible(true);
+ return (GenericSoundModel) method.invoke(soundModel);
+ } catch (ReflectiveOperationException e) {
+ Log.e(TAG, "Failed to getGenericSoundModel: " + soundModel, e);
+ return null;
+ }
}
/**
@@ -92,6 +113,16 @@
return true;
}
+ /**
+ * Get the current model state
+ *
+ * @param modelId The model ID to look-up the sound model for.
+ * @return 0 if the call succeeds, or an error code if it fails.
+ */
+ public int getModelState(UUID modelId) {
+ return mSoundTriggerManager.getModelState(modelId);
+ }
+
public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
SoundTriggerDetector.Callback callback) {
return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);