Merge "[Media ML] Let MCS manage MediaSession2" into sc-dev am: b8407d691c
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13530454
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I1f4aa6f73d8f747d9aede687b4de1dffa0b52836
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 481a712..d45cba9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21098,7 +21098,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;
@@ -21124,7 +21124,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;
@@ -21140,7 +21139,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();
@@ -21976,7 +21974,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();
@@ -21985,6 +21983,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();
@@ -22006,6 +22005,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
@@ -22139,7 +22139,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";
@@ -41956,6 +41955,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);
@@ -41968,6 +41968,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);
@@ -41979,6 +41980,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 c3d5d5a..2c93562 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -8870,6 +8870,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 d53d20a..f870400 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 29dea6b..98623c2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6799,7 +6799,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 74df1b2..4d4003f 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1303,7 +1303,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.
@@ -1314,7 +1318,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/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6e89faf..2f25acf 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 09d0af1..e67c9a4 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 7996f09..62da8a6 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5302,5 +5302,12 @@
* @hide
*/
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";
}
}
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index e66b17a..c43c410 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -77,3 +77,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 35d1d7b..19c6a62 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 Android.bp = file:platform/build/soong:/OWNERS
per-file android_animation_* = file:/core/java/android/animation/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-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 3fbfd7e..bad102e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1871,7 +1871,8 @@
<string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"Solicitação SS alterada para videochamada"</string>
<string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"Solicitação SS alterada para solicitação USSD"</string>
<string name="stk_cc_ss_to_ss" msgid="132040645206514450">"Alterada para uma nova solicitação SS"</string>
- <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Alerta de phishing"</string>
+ <!-- no translation found for notification_phishing_alert_content_description (494227305355958790) -->
+ <skip />
<string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil de trabalho"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"Alertado"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Expandir"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 9a7cb87..855d546 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1871,7 +1871,8 @@
<string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"SS درخواست کو ویڈیو کال میں تبدیل کر دیا گیا"</string>
<string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"SS درخواست کو USSD درخواست میں تبدیل کر دیا گیا"</string>
<string name="stk_cc_ss_to_ss" msgid="132040645206514450">"نئی SS درخواست میں تبدیل کر دیا گیا"</string>
- <string name="notification_phishing_alert_content_description" msgid="494227305355958790">"فریب دہی کا الرٹ"</string>
+ <!-- no translation found for notification_phishing_alert_content_description (494227305355958790) -->
+ <skip />
<string name="notification_work_profile_content_description" msgid="5296477955677725799">"دفتری پروفائل"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"الرٹ کیا گیا"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"پھیلائیں"</string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2004d0a..d8abf05 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3175,6 +3175,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/Android.bp b/data/etc/car/Android.bp
index f6d7685..2f41b90 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -173,3 +173,10 @@
src: "com.android.car.shell.xml",
filename_from_src: true,
}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.car.activityresolver",
+ sub_dir: "permissions",
+ src: "com.android.car.activityresolver.xml",
+ filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.activityresolver.xml b/data/etc/car/com.android.car.activityresolver.xml
new file mode 100644
index 0000000..d48bc15
--- /dev/null
+++ b/data/etc/car/com.android.car.activityresolver.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<permissions>
+ <privapp-permissions package="com.android.car.activityresolver">
+ <permission name="android.permission.MANAGE_USERS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index d1e94df..a927f53 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -36,11 +36,12 @@
java_test_host {
name: "error_prone_android_framework_test",
- test_suites: ["general-tests"],
srcs: ["tests/java/**/*.java"],
java_resource_dirs: ["tests/res"],
java_resources: [":error_prone_android_framework_testdata"],
static_libs: [
+ "truth-prebuilt",
+ "kxml2-2.3.0",
"error_prone_android_framework_lib",
"error_prone_test_helpers",
"hamcrest-library",
@@ -48,6 +49,9 @@
"platform-test-annotations",
"junit",
],
+ test_options: {
+ unit_test: true,
+ },
}
filegroup {
diff --git a/errorprone/TEST_MAPPING b/errorprone/TEST_MAPPING
deleted file mode 100644
index ee4552f..0000000
--- a/errorprone/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "error_prone_android_framework_test"
- }
- ]
-}
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 77ceda9..afd041b 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -649,6 +649,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/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 389fe7e..50eea31 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -105,7 +105,6 @@
LightingInfo::updateLighting(lightGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
- layerUpdateQueue->clear();
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -113,9 +112,14 @@
SkCanvas* profileCanvas = surface->getCanvas();
SkiaProfileRenderer profileRenderer(profileCanvas);
profiler->draw(profileRenderer);
- profileCanvas->flush();
}
+ {
+ ATRACE_NAME("flush commands");
+ surface->flushAndSubmit();
+ }
+ layerUpdateQueue->clear();
+
// Log memory statistics
if (CC_UNLIKELY(Properties::debugLevel != kDebugDisabled)) {
dumpResourceCacheUsage();
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6456e36..1f73ac9 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -454,9 +454,6 @@
renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform);
}
- ATRACE_NAME("flush commands");
- surface->flushAndSubmit();
-
Properties::skpCaptureEnabled = previousSkpEnabled;
}
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index ad6363b..1bd943f 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -17,14 +17,15 @@
#include "SkiaVulkanPipeline.h"
#include "DeferredLayerUpdater.h"
+#include "LightingInfo.h"
#include "Readback.h"
#include "ShaderCache.h"
-#include "LightingInfo.h"
#include "SkiaPipeline.h"
#include "SkiaProfileRenderer.h"
#include "VkInteropFunctorDrawable.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
+#include "utils/TraceUtils.h"
#include <SkSurface.h>
#include <SkTypes.h>
@@ -73,8 +74,6 @@
LightingInfo::updateLighting(lightGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer,
mVkSurface->getCurrentPreTransform());
- ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext());
- layerUpdateQueue->clear();
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
@@ -82,9 +81,14 @@
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkiaProfileRenderer profileRenderer(profileCanvas);
profiler->draw(profileRenderer);
- profileCanvas->flush();
}
+ {
+ ATRACE_NAME("flush commands");
+ mVkManager.finishFrame(backBuffer.get());
+ }
+ layerUpdateQueue->clear();
+
// Log memory statistics
if (CC_UNLIKELY(Properties::debugLevel != kDebugDisabled)) {
dumpResourceCacheUsage();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 3e7ce36..e93824d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -31,6 +31,7 @@
#include "Properties.h"
#include "RenderThread.h"
+#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
#include "utils/TraceUtils.h"
@@ -476,17 +477,10 @@
}
}
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
- if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
- ATRACE_NAME("Finishing GPU work");
- mDeviceWaitIdle(mDevice);
- }
-
- VulkanSurface::NativeBufferInfo* bufferInfo = surface->getCurrentBufferInfo();
- if (!bufferInfo) {
- // If VulkanSurface::dequeueNativeBuffer failed earlier, then swapBuffers is a no-op.
- return;
- }
+void VulkanManager::finishFrame(SkSurface* surface) {
+ ATRACE_NAME("Vulkan finish frame");
+ ALOGE_IF(mSwapSemaphore != VK_NULL_HANDLE || mDestroySemaphoreContext != nullptr,
+ "finishFrame already has an outstanding semaphore");
VkExportSemaphoreCreateInfo exportInfo;
exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
@@ -499,32 +493,52 @@
semaphoreInfo.flags = 0;
VkSemaphore semaphore;
VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to create semaphore");
+ ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
GrBackendSemaphore backendSemaphore;
backendSemaphore.initVulkan(semaphore);
- int fenceFd = -1;
- DestroySemaphoreInfo* destroyInfo =
- new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
GrFlushInfo flushInfo;
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = destroyInfo;
- GrSemaphoresSubmitted submitted = bufferInfo->skSurface->flush(
- SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
- GrDirectContext* context = GrAsDirectContext(bufferInfo->skSurface->recordingContext());
+ if (err == VK_SUCCESS) {
+ mDestroySemaphoreContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
+ flushInfo.fNumSemaphores = 1;
+ flushInfo.fSignalSemaphores = &backendSemaphore;
+ flushInfo.fFinishedProc = destroy_semaphore;
+ flushInfo.fFinishedContext = mDestroySemaphoreContext;
+ } else {
+ semaphore = VK_NULL_HANDLE;
+ }
+ GrSemaphoresSubmitted submitted =
+ surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
+ GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
ALOGE_IF(!context, "Surface is not backed by gpu");
context->submit();
- if (submitted == GrSemaphoresSubmitted::kYes) {
+ if (semaphore != VK_NULL_HANDLE) {
+ if (submitted == GrSemaphoresSubmitted::kYes) {
+ mSwapSemaphore = semaphore;
+ } else {
+ destroy_semaphore(mDestroySemaphoreContext);
+ mDestroySemaphoreContext = nullptr;
+ }
+ }
+ skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
+}
+
+void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
+ if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
+ ATRACE_NAME("Finishing GPU work");
+ mDeviceWaitIdle(mDevice);
+ }
+
+ int fenceFd = -1;
+ if (mSwapSemaphore != VK_NULL_HANDLE) {
VkSemaphoreGetFdInfoKHR getFdInfo;
getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
getFdInfo.pNext = nullptr;
- getFdInfo.semaphore = semaphore;
+ getFdInfo.semaphore = mSwapSemaphore;
getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
- err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+ VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
} else {
ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
@@ -532,9 +546,11 @@
std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
mQueueWaitIdle(mGraphicsQueue);
}
- destroy_semaphore(destroyInfo);
+ destroy_semaphore(mDestroySemaphoreContext);
surface->presentCurrentBuffer(dirtyRect, fenceFd);
+ mSwapSemaphore = VK_NULL_HANDLE;
+ mDestroySemaphoreContext = nullptr;
}
void VulkanManager::destroySurface(VulkanSurface* surface) {
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 121afc9..0912369 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -69,6 +69,7 @@
void destroySurface(VulkanSurface* surface);
Frame dequeueNextBuffer(VulkanSurface* surface);
+ void finishFrame(SkSurface* surface);
void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
// Inserts a wait on fence command into the Vulkan command buffer.
@@ -200,6 +201,9 @@
SwapBehavior mSwapBehavior = SwapBehavior::Discard;
GrVkExtensions mExtensions;
uint32_t mDriverVersion = 0;
+
+ VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
+ void* mDestroySemaphoreContext = nullptr;
};
} /* namespace renderthread */
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/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/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 303e5bb..5f56a3d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -752,7 +752,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-product/values-az/strings.xml b/packages/SystemUI/res-product/values-az/strings.xml
index ee86ae2..c0668db 100644
--- a/packages/SystemUI/res-product/values-az/strings.xml
+++ b/packages/SystemUI/res-product/values-az/strings.xml
@@ -38,8 +38,8 @@
<string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Telefonun kilidini açmaq üçün <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış cəhd etmisiniz. Daha <xliff:g id="NUMBER_1">%2$d</xliff:g> uğursuz cəhddən sonra iş profili silinəcək və bütün profil datası ləğv ediləcək."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Planşetin kilidini açmaq üçün <xliff:g id="NUMBER">%d</xliff:g> dəfə yanlış cəhd etmisiniz. İş profili silinəcək və bütün data ləğv ediləcək."</string>
<string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Telefonun kilidini açmaq üçün <xliff:g id="NUMBER">%d</xliff:g> dəfə yanlış cəhd etmisiniz. İş profili silinəcək və bütün data ləğv ediləcək."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Kilid açma modelini <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış çəkmisiniz. Daha <xliff:g id="NUMBER_1">%2$d</xliff:g> uğursuz cəhddən sonra planşet kilidini e-poçt hesabınızla açmaq tələb olunacaq.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniyə sonra yenidən cəhd edin."</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Kilid açma modelini artıq <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış çəkmisiniz. Daha <xliff:g id="NUMBER_1">%2$d</xliff:g> uğursuz cəhddən sonra telefon kilidini e-poçt hesabınızla açmaq tələb olunacaq.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniyə sonra yenidən cəhd edin."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Kilid açma modelini <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış çəkmisiniz. Daha <xliff:g id="NUMBER_1">%2$d</xliff:g> uğursuz cəhddən sonra planşet kilidini e-poçt hesabınızla açmaq tələb olunacaq.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniyə sonra cəhd edin."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Kilid açma modelini artıq <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış çəkmisiniz. Daha <xliff:g id="NUMBER_1">%2$d</xliff:g> uğursuz cəhddən sonra telefon kilidini e-poçt hesabınızla açmaq tələb olunacaq.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniyə sonra cəhd edin."</string>
<string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Daha çox seçim üçün telefonu kiliddən çıxarın"</string>
<string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Daha çox seçim üçün planşeti kiliddən çıxarın"</string>
<string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Daha çox seçim üçün cihazı kiliddən çıxarın"</string>
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..675d36f
--- /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_single_radius"
+ android:bottomRightRadius="0dp"
+ android:topLeftRadius="@dimen/accessibility_floating_menu_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..376b29d
--- /dev/null
+++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
@@ -0,0 +1,36 @@
+<?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:padding="@dimen/accessibility_floating_menu_padding"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <View
+ android:id="@+id/icon_view"
+ android:layout_width="@dimen/accessibility_floating_menu_width_height"
+ android:layout_height="@dimen/accessibility_floating_menu_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..fd3da23 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1361,6 +1361,16 @@
<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_width_height">36dp</dimen>
+ <dimen name="accessibility_floating_menu_single_radius">25dp</dimen>
+ <dimen name="accessibility_floating_menu_multiple_radius">20dp</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..f5356c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -0,0 +1,88 @@
+/*
+ * 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.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+
+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 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));
+ }
+ };
+
+ 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.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ mMenuView.show();
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ /* notifyForDescendants */ false, mContentObserver,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mMenuView.hide();
+
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+}
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..b393a89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -0,0 +1,195 @@
+/*
+ * 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.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.DimenRes;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+
+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 {
+ private static final float DEFAULT_LOCATION_Y_PERCENTAGE = 0.8f;
+ private static final int INDEX_MENU_ITEM = 0;
+ private boolean mIsShowing;
+ private RecyclerView mListView;
+ private final WindowManager.LayoutParams mLayoutParams;
+ private final WindowManager mWindowManager;
+ private final AccessibilityTargetAdapter mAdapter;
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ public AccessibilityFloatingMenuView(Context context) {
+ super(context);
+
+ mWindowManager = context.getSystemService(WindowManager.class);
+ mLayoutParams = createDefaultLayoutParams();
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
+
+ initListView(context, mAdapter);
+ updateStroke();
+ }
+
+ 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) {
+ mTargets.clear();
+ mTargets.addAll(newTargets);
+ mAdapter.notifyDataSetChanged();
+
+ final boolean hasMoreItems = mAdapter.getItemCount() > 1;
+ final int resId = hasMoreItems
+ ? R.dimen.accessibility_floating_menu_multiple_radius
+ : R.dimen.accessibility_floating_menu_single_radius;
+ setRadius(resId);
+ }
+
+ private void setRadius(@DimenRes int radiusResId) {
+ final float radius = getResources().getDimension(radiusResId);
+ final float[] radii = new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ getMenuGradientDrawable().setCornerRadii(radii);
+ }
+
+ private void initListView(Context context, AccessibilityTargetAdapter adapter) {
+ final Resources res = context.getResources();
+ final int margin =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
+ final int elevation =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
+ final Drawable listViewBackground =
+ context.getDrawable(R.drawable.accessibility_floating_menu_background);
+
+ mListView = new RecyclerView(context);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+ final LayoutParams layoutParams =
+ new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ layoutParams.setMarginsRelative(margin, margin, /* end= */ 0, margin);
+ mListView.setLayoutParams(layoutParams);
+ mListView.setElevation(elevation);
+ mListView.setBackground(listViewBackground);
+ mListView.setAdapter(adapter);
+ mListView.setLayoutManager(layoutManager);
+
+ addView(mListView);
+ }
+
+ 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);
+
+ updateLocation();
+ updateColor();
+ updateStroke();
+ }
+
+ private LayerDrawable getMenuLayerDrawable() {
+ return (LayerDrawable) mListView.getBackground();
+ }
+
+ private GradientDrawable getMenuGradientDrawable() {
+ return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
+ }
+
+ private void updateLocation() {
+ final DisplayMetrics dm = getResources().getDisplayMetrics();
+ mLayoutParams.x = dm.widthPixels;
+ mLayoutParams.y = (int) (dm.heightPixels * 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 updateStroke() {
+ final Resources res = getResources();
+ final boolean isNightMode =
+ (res.getConfiguration().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);
+ }
+}
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..8b586c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+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.util.List;
+
+/**
+ * An adapter which shows the set of accessibility targets that can be performed.
+ */
+public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
+
+ private final List<AccessibilityTarget> mTargets;
+ public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
+ mTargets = targets;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
+ final View root = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.accessibility_floating_menu_item, parent,
+ /* attachToRoot= */ false);
+
+ return new ViewHolder(root);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.mIconView.setBackground(mTargets.get(position).getIcon());
+
+ final boolean isFirstItem = (position == 0);
+ final boolean isLastItem = (position == (getItemCount() - 1));
+ final int padding = holder.itemView.getPaddingStart();
+ final int paddingTop = isFirstItem ? padding : 0;
+ final int paddingBottom = isLastItem ? padding : 0;
+ holder.itemView.setPaddingRelative(padding, paddingTop, padding, paddingBottom);
+ holder.itemView.setOnClickListener((v) -> mTargets.get(position).onSelected());
+ holder.mDivider.setVisibility(isLastItem ? View.GONE : View.VISIBLE);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTargets.size();
+ }
+
+ 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);
+ }
+ }
+}
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/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 01d3103..e5a960e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -83,6 +83,9 @@
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
+ /** Maximum allowed width or height for an icon drawable */
+ private static final int MAX_IMAGE_SIZE = 500;
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -378,6 +381,13 @@
Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
return false;
}
+
+ if (drawable.getIntrinsicWidth() > MAX_IMAGE_SIZE
+ || drawable.getIntrinsicHeight() > MAX_IMAGE_SIZE) {
+ Log.w(TAG, "Drawable is too large " + mIcon);
+ return false;
+ }
+
if (withClear) {
setImageDrawable(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5d02845..8bf466a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -168,6 +168,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 lockColors = 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..0489871
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+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<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..583a68b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.WindowManager;
+
+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)
+public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
+ private AccessibilityFloatingMenuView mMenuView;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ 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));
+ mMenuView = 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().getDimension(
+ R.dimen.accessibility_floating_menu_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);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 71f146b..f31639c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -35,6 +35,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
@@ -121,4 +122,13 @@
assertEquals("Transparent backgrounds should fallback to drawable color",
color, mIconView.getStaticDrawableColor());
}
+
+ @Test
+ public void testGiantImageNotAllowed() {
+ Bitmap largeBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
+ Icon icon = Icon.createWithBitmap(largeBitmap);
+ StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+ icon, 0, 0, "");
+ assertFalse(mIconView.set(largeIcon));
+ }
}
\ No newline at end of file
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/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java
index feed18d..3d8f5173 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/GesturesObserver.java
@@ -98,8 +98,7 @@
}
mProcessMotionEvent = true;
for (int i = 0; i < mGestureMatchers.size(); i++) {
- final GestureMatcher matcher =
- mGestureMatchers.get(i);
+ final GestureMatcher matcher = mGestureMatchers.get(i);
matcher.onMotionEvent(event, rawEvent, policyFlags);
if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
clear();
@@ -128,7 +127,10 @@
MotionEvent rawEvent, int policyFlags) {
if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags);
- //Clear the states in onMotionEvent().
+ // Ideally we clear the states in onMotionEvent(), this case is for hold gestures.
+ // If we clear before processing up event , then MultiTap matcher cancels the gesture
+ // due to incorrect state. It ends up listener#onGestureCancelled is called even
+ // the gesture is detected.
if (!mProcessMotionEvent) {
clear();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java
index 7a4d9e3..570e0ce 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureMatcher.java
@@ -33,7 +33,7 @@
class MagnificationGestureMatcher {
private static final int GESTURE_BASE = 100;
- public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;
+ public static final int GESTURE_TWO_FINGERS_DOWN_OR_SWIPE = GESTURE_BASE + 1;
public static final int GESTURE_SWIPE = GESTURE_BASE + 2;
public static final int GESTURE_SINGLE_TAP = GESTURE_BASE + 3;
public static final int GESTURE_SINGLE_TAP_AND_HOLD = GESTURE_BASE + 4;
@@ -41,7 +41,7 @@
public static final int GESTURE_TRIPLE_TAP_AND_HOLD = GESTURE_BASE + 6;
@IntDef(prefix = {"GESTURE_MAGNIFICATION_"}, value = {
- GESTURE_TWO_FINGER_DOWN,
+ GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
GESTURE_SWIPE
})
@Retention(RetentionPolicy.SOURCE)
@@ -57,8 +57,8 @@
switch (gestureId) {
case GESTURE_SWIPE:
return "GESTURE_SWIPE";
- case GESTURE_TWO_FINGER_DOWN:
- return "GESTURE_TWO_FINGER_DOWN";
+ case GESTURE_TWO_FINGERS_DOWN_OR_SWIPE:
+ return "GESTURE_TWO_FINGERS_DOWN_OR_SWIPE";
case GESTURE_SINGLE_TAP:
return "GESTURE_SINGLE_TAP";
case GESTURE_SINGLE_TAP_AND_HOLD:
@@ -71,6 +71,12 @@
return "none";
}
+ /**
+ * @param context
+ * @return the duration in milliseconds between the first tap's down event and
+ * the second tap's down event to be considered that the user is going to performing
+ * panning/scaling gesture.
+ */
static int getMagnificationMultiTapTimeout(Context context) {
return ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger(
R.integer.config_screen_magnification_multi_tap_adjustment);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java
index a209086..085c343 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGesturesObserver.java
@@ -65,7 +65,7 @@
* the last event before timeout.
*
* @see MagnificationGestureMatcher#GESTURE_SWIPE
- * @see MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN
+ * @see MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
*/
void onGestureCompleted(@GestureId int gestureId, long lastDownEventTime,
List<MotionEventInfo> delayedEventQueue, MotionEvent event);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java
index cd5061f..fa15ac1 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/SimpleSwipe.java
@@ -49,6 +49,11 @@
}
@Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (gestureMatched(event, rawEvent, policyFlags)) {
completeGesture(event, rawEvent, policyFlags);
@@ -65,7 +70,7 @@
}
private boolean gestureMatched(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- return mLastDown != null && (distance(mLastDown, event) >= mSwipeMinDistance);
+ return mLastDown != null && (distance(mLastDown, event) > mSwipeMinDistance);
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java
deleted file mode 100644
index 173a5b8..0000000
--- a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDown.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.server.accessibility.magnification;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.MotionEvent;
-
-import com.android.server.accessibility.gestures.GestureMatcher;
-
-/**
- *
- * This class is responsible for matching two fingers down gestures. The gesture matching
- * result is determined in a duration.
- */
-final class TwoFingersDown extends GestureMatcher {
-
- private MotionEvent mLastDown;
- private final int mDetectionDurationMillis;
-
- TwoFingersDown(Context context) {
- super(MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN,
- new Handler(context.getMainLooper()), null);
- mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
- context);
- }
-
- @Override
- protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- mLastDown = MotionEvent.obtain(event);
- cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
- }
-
- @Override
- protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (mLastDown == null) {
- cancelGesture(event, rawEvent, policyFlags);
- }
- completeGesture(event, rawEvent, policyFlags);
- }
-
- @Override
- protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- cancelGesture(event, rawEvent, policyFlags);
- }
-
- @Override
- public void clear() {
- if (mLastDown != null) {
- mLastDown.recycle();
- mLastDown = null;
- }
- super.clear();
- }
-
- @Override
- protected String getGestureName() {
- return this.getClass().getSimpleName();
- }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java
new file mode 100644
index 0000000..1742bd4
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/TwoFingersDownOrSwipe.java
@@ -0,0 +1,123 @@
+/*
+ * 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.accessibility.magnification;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.server.accessibility.gestures.GestureMatcher;
+
+/**
+ * This class is responsible for detecting that the user is using two fingers to perform
+ * swiping gestures or just stay pressed on the screen. The gesture matching result is determined
+ * in a duration.
+ */
+final class TwoFingersDownOrSwipe extends GestureMatcher {
+
+ private final int mDoubleTapTimeout;
+ private final int mDetectionDurationMillis;
+ private final int mSwipeMinDistance;
+ private MotionEvent mFirstPointerDown;
+ private MotionEvent mSecondPointerDown;
+
+ TwoFingersDownOrSwipe(Context context) {
+ super(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
+ new Handler(context.getMainLooper()), null);
+ mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
+ context);
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mFirstPointerDown = MotionEvent.obtain(event);
+ cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mFirstPointerDown == null) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ if (event.getPointerCount() == 2) {
+ mSecondPointerDown = MotionEvent.obtain(event);
+ completeAfter(mDoubleTapTimeout, event, rawEvent, policyFlags);
+ } else {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mFirstPointerDown == null || mSecondPointerDown == null) {
+ return;
+ }
+ if (distance(mFirstPointerDown, /* move */ event) > mSwipeMinDistance) {
+ completeGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ if (distance(mSecondPointerDown, /* move */ event) > mSwipeMinDistance) {
+ // The second pointer is swiping.
+ completeGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ public void clear() {
+ if (mFirstPointerDown != null) {
+ mFirstPointerDown.recycle();
+ mFirstPointerDown = null;
+ }
+ if (mSecondPointerDown != null) {
+ mSecondPointerDown.recycle();
+ mSecondPointerDown = null;
+ }
+ super.clear();
+ }
+
+ @Override
+ protected String getGestureName() {
+ return this.getClass().getSimpleName();
+ }
+
+ private static double distance(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) {
+ final int downActionIndex = downEvent.getActionIndex();
+ final int downPointerId = downEvent.getPointerId(downActionIndex);
+ final int moveActionIndex = moveEvent.findPointerIndex(downPointerId);
+ if (moveActionIndex < 0) {
+ return -1;
+ }
+ return MathUtils.dist(downEvent.getX(downActionIndex), downEvent.getY(downActionIndex),
+ moveEvent.getX(moveActionIndex), moveEvent.getY(moveActionIndex));
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 55a911e..fa34062 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -323,7 +323,7 @@
* manipulate the window magnifier or want to interact with current UI. The rule of leaving
* this state is as follows:
* <ol>
- * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN} is detected,
+ * <li> If {@link MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE} is detected,
* {@link State} will be transited to {@link PanningScalingGestureState}.</li>
* <li> If other gesture is detected and the last motion event is neither ACTION_UP nor
* ACTION_CANCEL.
@@ -357,7 +357,7 @@
new SimpleSwipe(context),
multiTap,
multiTapAndHold,
- new TwoFingersDown(context));
+ new TwoFingersDownOrSwipe(context));
}
@Override
@@ -399,7 +399,7 @@
Slog.d(mLogTag,
"onGestureDetected : delayedEventQueue = " + delayedEventQueue);
}
- if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN
+ if (gestureId == MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
&& mWindowMagnificationMgr.pointersInWindow(mDisplayId, motionEvent) > 0) {
transitionTo(mObservePanningScalingState);
} else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) {
diff --git a/services/api/Android.bp b/services/api/Android.bp
new file mode 100644
index 0000000..b8ca548
--- /dev/null
+++ b/services/api/Android.bp
@@ -0,0 +1,29 @@
+// 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"],
+}
+
+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"],
+}
\ No newline at end of file
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 21cae45..4744b9f 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/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 e5ef935..6f7099c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -160,6 +160,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;
@@ -559,6 +560,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 7cd49497..bc890d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15871,6 +15871,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 8d6bcad..0087f18 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;
}
@@ -1143,7 +1142,7 @@
// Seq #23
if (isTailOfActivePath(path, getActivePath())) {
int newPath = mService.portIdToPath(getActivePortId());
- startRoutingControl(getActivePath(), newPath, true, null);
+ startRoutingControl(getActivePath(), newPath, null);
}
}
@@ -1161,7 +1160,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 274344a..4923d96 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2152,14 +2152,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);
@@ -3563,15 +3565,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),
@@ -3584,19 +3601,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();
@@ -3627,13 +3648,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/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index e18516d..62c155a 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -540,7 +540,7 @@
setStatusBarState(mLockTaskModeState, userId);
setKeyguardState(mLockTaskModeState, userId);
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- lockKeyguardIfNeeded();
+ lockKeyguardIfNeeded(userId);
}
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
@@ -886,15 +886,15 @@
* Helper method for locking the device immediately. This may be necessary when the device
* leaves the pinned mode.
*/
- private void lockKeyguardIfNeeded() {
- if (shouldLockKeyguard()) {
+ private void lockKeyguardIfNeeded(int userId) {
+ if (shouldLockKeyguard(userId)) {
mWindowManager.lockNow(null);
mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
getLockPatternUtils().requireCredentialEntry(USER_ALL);
}
}
- private boolean shouldLockKeyguard() {
+ private boolean shouldLockKeyguard(int userId) {
// This functionality should be kept consistent with
// com.android.settings.security.ScreenPinningSettings (see b/127605586)
try {
@@ -904,7 +904,7 @@
} catch (Settings.SettingNotFoundException e) {
// Log to SafetyNet for b/127605586
android.util.EventLog.writeEvent(0x534e4554, "127605586", -1, "");
- return getLockPatternUtils().isSecure(USER_CURRENT);
+ return getLockPatternUtils().isSecure(userId);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 82d9c21..7edb525 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -180,6 +180,7 @@
} else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
} else if (layout != null && canApplyFreeformPolicy) {
+ mTmpBounds.set(currentParams.mBounds);
getLayoutBounds(display, root, layout, mTmpBounds);
if (!mTmpBounds.isEmpty()) {
launchMode = WINDOWING_MODE_FREEFORM;
@@ -500,11 +501,11 @@
}
private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root,
- @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) {
+ @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect inOutBounds) {
final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if (!windowLayout.hasSpecifiedSize() && verticalGravity == 0 && horizontalGravity == 0) {
- outBounds.setEmpty();
+ inOutBounds.setEmpty();
return;
}
@@ -518,11 +519,17 @@
int width;
int height;
if (!windowLayout.hasSpecifiedSize()) {
- outBounds.setEmpty();
- getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM,
- /* hasInitialBounds */ false, outBounds);
- width = outBounds.width();
- height = outBounds.height();
+ if (!inOutBounds.isEmpty()) {
+ // If the bounds is resolved already and WindowLayout doesn't have any opinion on
+ // its size, use the already resolved size and apply the gravity to it.
+ width = inOutBounds.width();
+ height = inOutBounds.height();
+ } else {
+ getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM,
+ /* hasInitialBounds */ false, inOutBounds);
+ width = inOutBounds.width();
+ height = inOutBounds.height();
+ }
} else {
width = defaultWidth;
if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
@@ -563,11 +570,11 @@
fractionOfVerticalOffset = 0.5f;
}
- outBounds.set(0, 0, width, height);
- outBounds.offset(displayStableBounds.left, displayStableBounds.top);
+ inOutBounds.set(0, 0, width, height);
+ inOutBounds.offset(displayStableBounds.left, displayStableBounds.top);
final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width));
final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height));
- outBounds.offset(xOffset, yOffset);
+ inOutBounds.offset(xOffset, yOffset);
}
private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 524892b..33a0903 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8725,6 +8725,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/accessibility/magnification/MagnificationGesturesObserverTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java
index 895fb17..5dbf837 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGesturesObserverTest.java
@@ -69,7 +69,7 @@
mContext = InstrumentationRegistry.getContext();
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mObserver = new MagnificationGesturesObserver(mCallback, new SimpleSwipe(mContext),
- new TwoFingersDown(mContext));
+ new TwoFingersDownOrSwipe(mContext));
}
@Test
@@ -77,9 +77,7 @@
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
DEFAULT_X , DEFAULT_Y);
- mInstrumentation.runOnMainSync(() -> {
- mObserver.onMotionEvent(moveEvent, moveEvent, 0);
- });
+ mObserver.onMotionEvent(moveEvent, moveEvent, 0);
verify(mCallback).onGestureCancelled(eq(0L),
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(moveEvent)));
@@ -92,9 +90,7 @@
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
DEFAULT_X , DEFAULT_Y);
- mInstrumentation.runOnMainSync(() -> {
- mObserver.onMotionEvent(downEvent, downEvent, 0);
- });
+ mObserver.onMotionEvent(downEvent, downEvent, 0);
verify(mCallback).onGestureCancelled(eq(0L),
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent)));
@@ -108,9 +104,7 @@
final int timeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
mContext) + 100;
- mInstrumentation.runOnMainSync(() -> {
- mObserver.onMotionEvent(downEvent, downEvent, 0);
- });
+ mObserver.onMotionEvent(downEvent, downEvent, 0);
verify(mCallback, timeout(timeoutMillis)).onGestureCancelled(eq(downEvent.getDownTime()),
mEventInfoArgumentCaptor.capture(), argThat(new MotionEventMatcher(downEvent)));
@@ -121,14 +115,12 @@
public void sendEventsOfSwiping_onGestureCompleted() {
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
DEFAULT_X, DEFAULT_Y);
- final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
DEFAULT_X + swipeDistance, DEFAULT_Y + swipeDistance);
- mInstrumentation.runOnMainSync(() -> {
- mObserver.onMotionEvent(downEvent, downEvent, 0);
- mObserver.onMotionEvent(moveEvent, moveEvent, 0);
- });
+ mObserver.onMotionEvent(downEvent, downEvent, 0);
+ mObserver.onMotionEvent(moveEvent, moveEvent, 0);
verify(mCallback).onGestureCompleted(eq(MagnificationGestureMatcher.GESTURE_SWIPE),
eq(downEvent.getDownTime()), mEventInfoArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java
index 01631bf21..0ca631e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/SimpleSwipeTest.java
@@ -78,7 +78,7 @@
@Test
public void sendSwipeEvent_onGestureCompleted() {
- final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ final float swipeDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
DEFAULT_X, DEFAULT_Y);
final MotionEvent moveEvent = TouchEventGenerator.moveEvent(Display.DEFAULT_DISPLAY,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java
new file mode 100644
index 0000000..162d2a9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.accessibility.magnification;
+
+import static com.android.server.accessibility.utils.TouchEventGenerator.movePointer;
+import static com.android.server.accessibility.utils.TouchEventGenerator.twoPointersDownEvents;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.accessibility.utils.TouchEventGenerator;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Tests for {@link TwoFingersDownOrSwipe}.
+ */
+public class TwoFingersDownOrSwipeTest {
+
+ private static final float DEFAULT_X = 100f;
+ private static final float DEFAULT_Y = 100f;
+
+ private static float sSwipeMinDistance;
+ private static int sTimeoutMillis;
+ private static Context sContext;
+
+ private GesturesObserver mGesturesObserver;
+ @Mock
+ private GesturesObserver.Listener mListener;
+
+ @BeforeClass
+ public static void setupOnce() {
+ sContext = InstrumentationRegistry.getContext();
+ sTimeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
+ sContext) + 100;
+ sSwipeMinDistance = ViewConfiguration.get(sContext).getScaledTouchSlop() + 1;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDownOrSwipe(sContext));
+ }
+
+ @Test
+ public void sendSingleDownEvent_GestureCanceledAfterTimeout() {
+ final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
+ DEFAULT_X, DEFAULT_Y);
+
+ mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
+
+ verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class),
+ any(MotionEvent.class), eq(0));
+ }
+
+ @Test
+ public void sendTwoFingerDownEvent_onGestureCompleted() {
+ final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
+ new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
+
+ for (MotionEvent event : downEvents) {
+ mGesturesObserver.onMotionEvent(event, event, 0);
+ }
+
+ verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
+ MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, downEvents.get(1),
+ downEvents.get(1), 0);
+ }
+
+ @Test
+ public void sendSingleTapEvent_onGestureCancelled() {
+ final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
+ DEFAULT_X, DEFAULT_Y);
+ final MotionEvent upEvent = TouchEventGenerator.upEvent(Display.DEFAULT_DISPLAY,
+ DEFAULT_X, DEFAULT_Y);
+
+ mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
+ mGesturesObserver.onMotionEvent(upEvent, upEvent, 0);
+
+ verify(mListener, after(ViewConfiguration.getDoubleTapTimeout())).onGestureCancelled(
+ any(MotionEvent.class), any(MotionEvent.class), eq(0));
+ }
+
+ @Test
+ public void firstPointerMove_twoPointersDown_onGestureCompleted() {
+ final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
+ new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
+ for (MotionEvent event : downEvents) {
+ mGesturesObserver.onMotionEvent(event, event, 0);
+ }
+ final MotionEvent moveEvent = movePointer(downEvents.get(1), 0, sSwipeMinDistance, 0);
+
+ mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0);
+
+ verify(mListener).onGestureCompleted(
+ MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent,
+ moveEvent, 0);
+ }
+
+ @Test
+ public void secondPointerMove_twoPointersDown_onGestureCompleted() {
+ final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
+ new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
+ for (MotionEvent event : downEvents) {
+ mGesturesObserver.onMotionEvent(event, event, 0);
+ }
+ final MotionEvent moveEvent = movePointer(downEvents.get(1), 1, sSwipeMinDistance, 0);
+
+ mGesturesObserver.onMotionEvent(moveEvent, moveEvent, 0);
+
+ verify(mListener).onGestureCompleted(
+ MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, moveEvent,
+ moveEvent, 0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
deleted file mode 100644
index ed8dc4e..0000000
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.server.accessibility.magnification;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.view.Display;
-import android.view.MotionEvent;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.server.accessibility.utils.TouchEventGenerator;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link TwoFingersDown}.
- */
-public class TwoFingersDownTest {
-
- private static final float DEFAULT_X = 100f;
- private static final float DEFAULT_Y = 100f;
-
- private static Context sContext;
- private static int sTimeoutMillis;
-
- private Context mContext;
- private GesturesObserver mGesturesObserver;
- @Mock
- private GesturesObserver.Listener mListener;
-
- @BeforeClass
- public static void setupOnce() {
- sContext = InstrumentationRegistry.getContext();
- sTimeoutMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
- sContext) + 100;
- }
-
- @Before
- public void setUp() {
- mContext = InstrumentationRegistry.getContext();
- MockitoAnnotations.initMocks(this);
- mGesturesObserver = new GesturesObserver(mListener, new TwoFingersDown(mContext));
- }
-
- @Test
- public void sendSingleDownEvent_GestureCanceledAfterTimeout() {
- final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
- DEFAULT_X, DEFAULT_Y);
-
- mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
-
- verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class),
- any(MotionEvent.class), eq(0));
- }
-
- @Test
- public void sendTwoFingerDownEvent_onGestureCompleted() {
- final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
- DEFAULT_X, DEFAULT_Y);
- final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
- defPointerCoords.x = DEFAULT_X;
- defPointerCoords.y = DEFAULT_Y;
- final MotionEvent.PointerCoords secondPointerCoords = new MotionEvent.PointerCoords();
- secondPointerCoords.x = DEFAULT_X + 10;
- secondPointerCoords.y = DEFAULT_Y + 10;
-
- final MotionEvent twoPointersDownEvent = TouchEventGenerator.twoPointersDownEvent(
- Display.DEFAULT_DISPLAY, defPointerCoords, secondPointerCoords);
-
- mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
- mGesturesObserver.onMotionEvent(twoPointersDownEvent, twoPointersDownEvent, 0);
-
- verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
- MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN, twoPointersDownEvent,
- twoPointersDownEvent, 0);
- }
-
- @Test
- public void sendSingleTapEvent_onGestureCancelled() {
- final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY,
- DEFAULT_X, DEFAULT_Y);
- final MotionEvent upEvent = TouchEventGenerator.upEvent(Display.DEFAULT_DISPLAY,
- DEFAULT_X, DEFAULT_Y);
-
- mGesturesObserver.onMotionEvent(downEvent, downEvent, 0);
- mGesturesObserver.onMotionEvent(upEvent, upEvent, 0);
-
- verify(mListener, timeout(sTimeoutMillis)).onGestureCancelled(any(MotionEvent.class),
- any(MotionEvent.class), eq(0));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index 4b7ebbc..b9498d6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -24,13 +24,15 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.DebugUtils;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.EventStreamTransformation;
@@ -43,6 +45,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.function.IntConsumer;
/**
@@ -75,7 +78,7 @@
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getContext();
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0,
mock(WindowMagnificationManager.Callback.class));
mMockConnection = new MockWindowMagnificationConnection();
@@ -100,8 +103,8 @@
* Covers following paths to get to and back between each state and {@link #STATE_IDLE}.
* <p>
* <br> IDLE -> SHOW_MAGNIFIER [label="a11y\nbtn"]
- * <br> SHOW_MAGNIFIER -> TWO_FINGER_DOWN [label="2hold"]
- * <br> TWO_FINGER_DOWN -> SHOW_MAGNIFIER [label="release"]
+ * <br> SHOW_MAGNIFIER -> TWO_FINGERS_DOWN [label="2hold"]
+ * <br> TWO_FINGERS_DOWN -> SHOW_MAGNIFIER [label="release"]
* <br> SHOW_MAGNIFIER -> IDLE [label="a11y\nbtn"]
* <br> IDLE -> SHOW_MAGNIFIER_TRIPLE_TAP [label="3tap"]
* <br> SHOW_MAGNIFIER_TRIPLE_TAP -> IDLE [label="3tap"]
@@ -112,18 +115,16 @@
*/
@Test
public void testEachState_isReachableAndRecoverable() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- forEachState(state -> {
- goFromStateIdleTo(state);
- assertIn(state);
- returnToNormalFrom(state);
- try {
- assertIn(STATE_IDLE);
- } catch (AssertionError e) {
- throw new AssertionError("Failed while testing state " + stateToString(state),
- e);
- }
- });
+ forEachState(state -> {
+ goFromStateIdleTo(state);
+ assertIn(state);
+ returnToNormalFrom(state);
+ try {
+ assertIn(STATE_IDLE);
+ } catch (AssertionError e) {
+ throw new AssertionError("Failed while testing state " + stateToString(state),
+ e);
+ }
});
}
@@ -209,10 +210,19 @@
case STATE_TWO_FINGERS_DOWN: {
goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT);
final Rect frame = mMockConnection.getMirrorWindowFrame();
- send(downEvent(frame.centerX(), frame.centerY()));
- //Second finger is outside the window.
- send(twoPointerDownEvent(new float[]{frame.centerX(), frame.centerX() + 10},
- new float[]{frame.centerY(), frame.centerY() + 10}));
+ final PointF firstPointerDown = new PointF(frame.centerX(), frame.centerY());
+ // The second finger is outside the window.
+ final PointF secondPointerDown = new PointF(frame.right + 10,
+ frame.bottom + 10);
+ final List<MotionEvent> motionEvents =
+ TouchEventGenerator.twoPointersDownEvents(DISPLAY_0,
+ firstPointerDown, secondPointerDown);
+ for (MotionEvent downEvent: motionEvents) {
+ send(downEvent);
+ }
+ // Wait for two-finger down gesture completed.
+ Thread.sleep(ViewConfiguration.getDoubleTapTimeout());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
break;
case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: {
@@ -301,16 +311,6 @@
send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y));
}
- private MotionEvent twoPointerDownEvent(float[] x, float[] y) {
- final MotionEvent.PointerCoords defPointerCoords = new MotionEvent.PointerCoords();
- defPointerCoords.x = x[0];
- defPointerCoords.y = y[0];
- final MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
- pointerCoords.x = x[1];
- pointerCoords.y = y[1];
- return TouchEventGenerator.twoPointersDownEvent(DISPLAY_0, defPointerCoords, pointerCoords);
- }
-
private String stateDump() {
return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
index a05881f..fbcde53 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/TouchEventGenerator.java
@@ -19,16 +19,19 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
-import static android.view.MotionEvent.PointerCoords;
+import android.graphics.PointF;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.MotionEvent;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* generates {@link MotionEvent} with source {@link InputDevice#SOURCE_TOUCHSCREEN}
- *
*/
public class TouchEventGenerator {
@@ -39,44 +42,68 @@
public static MotionEvent moveEvent(int displayId, float x, float y) {
return generateSingleTouchEvent(displayId, ACTION_MOVE, x, y);
}
+
public static MotionEvent upEvent(int displayId, float x, float y) {
return generateSingleTouchEvent(displayId, ACTION_UP, x, y);
}
- public static MotionEvent twoPointersDownEvent(int displayId, PointerCoords defPointerCoords,
- PointerCoords pointerCoords) {
- return generateTwoPointersEvent(displayId, ACTION_POINTER_DOWN, defPointerCoords,
- pointerCoords);
- }
-
private static MotionEvent generateSingleTouchEvent(int displayId, int action, float x,
float y) {
- final long downTime = SystemClock.uptimeMillis();
- final MotionEvent ev = MotionEvent.obtain(downTime, downTime,
- action, x, y, 0);
- ev.setDisplayId(displayId);
- ev.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- return ev;
+ return generateMultiplePointersEvent(displayId, action, new PointF(x, y));
}
- private static MotionEvent generateTwoPointersEvent(int displayId, int action,
- PointerCoords defPointerCoords, PointerCoords pointerCoords) {
- final long downTime = SystemClock.uptimeMillis();
- MotionEvent.PointerProperties defPointerProperties = new MotionEvent.PointerProperties();
- defPointerProperties.id = 0;
- defPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
- MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
- pointerProperties.id = 1;
- pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ /**
+ * Creates a list of {@link MotionEvent} with given pointers location.
+ *
+ * @param displayId the display id
+ * @param pointF1 location on the screen of the second pointer.
+ * @param pointF2 location on the screen of the second pointer.
+ * @return a list of {@link MotionEvent} with {@link MotionEvent#ACTION_DOWN} and {@link
+ * MotionEvent#ACTION_POINTER_DOWN}.
+ */
+ public static List<MotionEvent> twoPointersDownEvents(int displayId, PointF pointF1,
+ PointF pointF2) {
+ final List<MotionEvent> downEvents = new ArrayList<>();
+ final MotionEvent downEvent = generateMultiplePointersEvent(displayId,
+ MotionEvent.ACTION_DOWN, pointF1);
+ downEvents.add(downEvent);
+ final int actionIndex = 1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int action = ACTION_POINTER_DOWN | actionIndex;
+
+ final MotionEvent twoPointersDownEvent = generateMultiplePointersEvent(displayId, action,
+ pointF1, pointF2);
+ downEvents.add(twoPointersDownEvent);
+ return downEvents;
+ }
+
+ private static MotionEvent generateMultiplePointersEvent(int displayId, int action,
+ PointF... pointFs) {
+ final int length = pointFs.length;
+ final MotionEvent.PointerCoords[] pointerCoordsArray =
+ new MotionEvent.PointerCoords[length];
+ final MotionEvent.PointerProperties[] pointerPropertiesArray =
+ new MotionEvent.PointerProperties[length];
+ for (int i = 0; i < length; i++) {
+ MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
+ pointerCoords.x = pointFs[i].x;
+ pointerCoords.y = pointFs[i].y;
+ pointerCoordsArray[i] = pointerCoords;
+
+ MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
+ pointerProperties.id = i;
+ pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+ pointerPropertiesArray[i] = pointerProperties;
+ }
+
+ final long downTime = SystemClock.uptimeMillis();
final MotionEvent ev = MotionEvent.obtain(
/* downTime */ downTime,
/* eventTime */ downTime,
/* action */ action,
- /* pointerCount */ 2,
- /* pointerProperties */ new MotionEvent.PointerProperties[] {
- defPointerProperties, pointerProperties},
- /* pointerCoords */ new PointerCoords[] { defPointerCoords, pointerCoords },
+ /* pointerCount */ length,
+ /* pointerProperties */ pointerPropertiesArray,
+ /* pointerCoords */ pointerCoordsArray,
/* metaState */ 0,
/* buttonState */ 0,
/* xPrecision */ 1.0f,
@@ -88,4 +115,65 @@
ev.setDisplayId(displayId);
return ev;
}
+
+ /**
+ * Generates a move event that moves the pointer of the original event with given index.
+ * The original event should not be up event and we don't support
+ * {@link MotionEvent#ACTION_POINTER_UP} now.
+ *
+ * @param originalEvent the move or down event
+ * @param pointerIndex the index of the pointer we want to move.
+ * @param offsetX the offset in X coordinate.
+ * @param offsetY the offset in Y coordinate.
+ * @return a motion event with move action.
+ */
+ public static MotionEvent movePointer(MotionEvent originalEvent, int pointerIndex,
+ float offsetX, float offsetY) {
+ if (originalEvent.getActionMasked() == ACTION_UP) {
+ throw new IllegalArgumentException("No pointer is on the screen");
+ }
+
+ if (originalEvent.getActionMasked() == ACTION_POINTER_UP) {
+ throw new IllegalArgumentException("unsupported yet,please implement it first");
+ }
+
+ final int pointerCount = originalEvent.getPointerCount();
+ if (pointerIndex >= pointerCount) {
+ throw new IllegalArgumentException(
+ pointerIndex + "is not available with pointer count" + pointerCount);
+ }
+ final int action = MotionEvent.ACTION_MOVE;
+ final MotionEvent.PointerProperties[] pp = new MotionEvent.PointerProperties[pointerCount];
+ for (int i = 0; i < pointerCount; i++) {
+ MotionEvent.PointerProperties pointerProperty = new MotionEvent.PointerProperties();
+ originalEvent.getPointerProperties(i, pointerProperty);
+ pp[i] = pointerProperty;
+ }
+
+ final MotionEvent.PointerCoords[] pc = new MotionEvent.PointerCoords[pointerCount];
+ for (int i = 0; i < pointerCount; i++) {
+ MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
+ originalEvent.getPointerCoords(i, pointerCoord);
+ pc[i] = pointerCoord;
+ }
+ pc[pointerIndex].x += offsetX;
+ pc[pointerIndex].y += offsetY;
+ final MotionEvent ev = MotionEvent.obtain(
+ /* downTime */ originalEvent.getDownTime(),
+ /* eventTime */ SystemClock.uptimeMillis(),
+ /* action */ action,
+ /* pointerCount */ 2,
+ /* pointerProperties */ pp,
+ /* pointerCoords */ pc,
+ /* metaState */ 0,
+ /* buttonState */ 0,
+ /* xPrecision */ 1.0f,
+ /* yPrecision */ 1.0f,
+ /* deviceId */ 0,
+ /* edgeFlags */ 0,
+ /* source */ originalEvent.getSource(),
+ /* flags */ originalEvent.getFlags());
+ ev.setDisplayId(originalEvent.getDisplayId());
+ return ev;
+ }
}
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/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index d663b64..cc1869e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -454,7 +454,7 @@
Settings.Secure.clearProviderForTest();
// AND a password is set
- when(mLockPatternUtils.isSecure(anyInt()))
+ when(mLockPatternUtils.isSecure(TEST_USER_ID))
.thenReturn(true);
// AND there is a task record
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index f8346efb..8d38f09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -521,6 +521,7 @@
WINDOWING_MODE_FULLSCREEN);
}
+
@Test
public void testKeepsPictureInPictureLaunchModeInOptions() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
@@ -588,11 +589,14 @@
}
@Test
- public void testNonEmptyLayoutInfersFreeformWithEmptySize() {
+ public void testLayoutWithGravityAndEmptySizeInfersFreeformAndRespectsCurrentSize() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
+ final Rect expectedLaunchBounds = new Rect(0, 0, 200, 100);
+
mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ mCurrent.mBounds.set(expectedLaunchBounds);
final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
.setGravity(Gravity.LEFT).build();
@@ -600,6 +604,9 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setLayout(layout).calculate());
+ assertEquals(expectedLaunchBounds.width(), mResult.mBounds.width());
+ assertEquals(expectedLaunchBounds.height(), mResult.mBounds.height());
+
assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
WINDOWING_MODE_FREEFORM);
}
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 1473b7a..e1f3451 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>
*/
@@ -3366,6 +3403,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);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1cfc3f9..ad87567 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -407,6 +407,8 @@
private QosCallbackMockHelper mQosCallbackMockHelper;
private QosCallbackTracker mQosCallbackTracker;
private VpnManagerService mVpnManagerService;
+ private TestNetworkCallback mDefaultNetworkCallback;
+ private TestNetworkCallback mSystemDefaultNetworkCallback;
// State variables required to emulate NetworkPolicyManagerService behaviour.
private int mUidRules = RULE_NONE;
@@ -1547,6 +1549,7 @@
@After
public void tearDown() throws Exception {
+ unregisterDefaultNetworkCallbacks();
setAlwaysOnNetworks(false);
if (mCellNetworkAgent != null) {
mCellNetworkAgent.disconnect();
@@ -9467,6 +9470,10 @@
fail("TOO_MANY_REQUESTS never thrown");
}
+ private UidRange createUidRange(int userId) {
+ return UidRange.createForUser(UserHandle.of(userId));
+ }
+
private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid)
throws Exception {
final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -9801,6 +9808,54 @@
assertEquals(expectedPerAppNetwork, defaultNetwork);
assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size());
}
+ verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork);
+ }
+
+ /**
+ * Verify default callbacks for 'available' fire as expected. This will only run if
+ * registerDefaultNetworkCallbacks() was executed prior and will only be different if the
+ * setOemNetworkPreference() per-app API was used for the current process.
+ * @param expectedSystemDefault the expected network for the system default.
+ * @param expectedPerAppDefault the expected network for the current process's default.
+ */
+ private void verifyMultipleDefaultCallbacks(
+ @NonNull final Network expectedSystemDefault,
+ @NonNull final Network expectedPerAppDefault) {
+ if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault
+ && mService.mNoServiceNetwork.network() != expectedSystemDefault) {
+ // getLastAvailableNetwork() is used as this method can be called successively with
+ // the same network to validate therefore expectAvailableThenValidatedCallbacks
+ // can't be used.
+ assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(),
+ expectedSystemDefault);
+ }
+ if (null != mDefaultNetworkCallback && null != expectedPerAppDefault
+ && mService.mNoServiceNetwork.network() != expectedPerAppDefault) {
+ assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(),
+ expectedPerAppDefault);
+ }
+ }
+
+ private void registerDefaultNetworkCallbacks() {
+ // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback()
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mSystemDefaultNetworkCallback = new TestNetworkCallback();
+ mDefaultNetworkCallback = new TestNetworkCallback();
+ mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
+ new Handler(ConnectivityThread.getInstanceLooper()));
+ mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+ }
+
+ private void unregisterDefaultNetworkCallbacks() {
+ if (null != mDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
+ }
+ if (null != mSystemDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -9884,6 +9939,7 @@
@OemNetworkPreferences.OemNetworkPreference final int networkPref =
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
// Setup the test process to use networkPref for their default network.
setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
@@ -9898,6 +9954,7 @@
// Verify that the active network is correct
verifyActiveNetwork(TRANSPORT_ETHERNET);
+ // default NCs will be unregistered in tearDown
}
@Test
@@ -9905,6 +9962,7 @@
@OemNetworkPreferences.OemNetworkPreference final int networkPref =
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
// Setup the test process to use networkPref for their default network.
setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
@@ -9925,6 +9983,7 @@
mEthernetNetworkAgent.getNetwork());
assertFalse(mCm.isActiveNetworkMetered());
+ // default NCs will be unregistered in tearDown
}
@Test
@@ -10081,7 +10140,6 @@
/**
* Test the tracked default requests clear previous OEM requests on setOemNetworkPreference().
- * @throws Exception
*/
@Test
public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception {
@@ -10109,9 +10167,8 @@
}
/**
- * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order:
+ * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order:
* NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly()
@@ -10177,9 +10234,8 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
* NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly()
@@ -10240,10 +10296,9 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order:
* NET_CAPABILITY_OEM_PAID
* This preference should only apply to OEM_PAID networks.
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly()
@@ -10294,10 +10349,9 @@
}
/**
- * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order:
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order:
* NET_CAPABILITY_OEM_PRIVATE
* This preference should only apply to OEM_PRIVATE networks.
- * @throws Exception
*/
@Test
public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly()
@@ -10347,7 +10401,235 @@
true /* shouldDestroyNetwork */);
}
- private UidRange createUidRange(int userId) {
- return UidRange.createForUser(UserHandle.of(userId));
+ /**
+ * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order:
+ * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 3;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mCellNetworkAgent.getNetwork());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mWiFiNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID will put both on null as it is the last network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ null);
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
+ * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 2;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network but not the pref.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mWiFiNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order:
+ * NET_CAPABILITY_OEM_PAID
+ * This preference should only apply to OEM_PAID networks.
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID.
+ // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Disconnecting cellular will put the fallback on null and the pref on disconnected.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
+ }
+
+ /**
+ * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order:
+ * NET_CAPABILITY_OEM_PRIVATE
+ * This preference should only apply to OEM_PRIVATE networks.
+ */
+ @Test
+ public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly()
+ throws Exception {
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+ final int expectedDefaultRequestSize = 2;
+ final int expectedOemPrefRequestSize = 1;
+ registerDefaultNetworkCallbacks();
+
+ // The fallback as well as the OEM preference should now be tracked.
+ assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size());
+
+ // Test lowest to highest priority requests.
+ // Bring up metered cellular. This will satisfy the fallback network.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE.
+ startOemManagedNetwork(false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mWiFiNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mEthernetNetworkAgent.getNetwork());
+
+ // Disconnecting OEM_PRIVATE will keep the fallback on cellular.
+ // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network.
+ stopOemManagedNetwork();
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ mCellNetworkAgent.getNetwork(),
+ mService.mNoServiceNetwork.network());
+
+ // Disconnecting cellular will put the fallback on null and pref on disconnected.
+ setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
+ verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
+ null,
+ mService.mNoServiceNetwork.network());
+
+ // default NCs will be unregistered in tearDown
}
}