Merge "Clean up outdated path of sleep token for swapping display" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 5e9af6a..a16aa2d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -411,6 +411,13 @@
host_supported: true,
}
+cc_aconfig_library {
+ name: "android.companion.virtualdevice.flags-aconfig-cc-test",
+ aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
+ host_supported: true,
+ mode: "test",
+}
+
java_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-java",
aconfig_declarations: "android.companion.virtualdevice.flags-aconfig",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0947e33..5a3a8d5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
@@ -7297,6 +7298,9 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void finish(int finishTask) {
+ if (DEBUG_FINISH_ACTIVITY) {
+ Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable());
+ }
if (mParent == null) {
int resultCode;
Intent resultData;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index db216b1..be27046 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -107,6 +107,8 @@
// If set, will print the stack trace for activity starts within the process
static final boolean DEBUG_START_ACTIVITY = Build.IS_DEBUGGABLE &&
SystemProperties.getBoolean("persist.wm.debug.start_activity", false);
+ static final boolean DEBUG_FINISH_ACTIVITY = Build.IS_DEBUGGABLE &&
+ SystemProperties.getBoolean("persist.wm.debug.finish_activity", false);
/**
* @hide
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index b63e2cf..d05d23c 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -103,3 +103,10 @@
description: "API for on-demand rotation of virtual displays"
bug: "291748430"
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "high_resolution_scroll"
+ description: "Enable high resolution scroll"
+ bug: "335160780"
+}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 5953890..93724bb 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -136,6 +136,14 @@
"android.intent.extra.LOGGING_INSTANCE_ID";
/**
+ * The id of the task containing the window that initiated the drag that should be hidden.
+ * Only provided to internal drag handlers as a part of the DRAG_START event.
+ * @hide
+ */
+ public static final String EXTRA_HIDE_DRAG_SOURCE_TASK_ID =
+ "android.intent.extra.HIDE_DRAG_SOURCE_TASK_ID";
+
+ /**
* Indicates that a ClipData contains potentially sensitive information, such as a
* password or credit card number.
* <p>
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index ba356bb..6514872 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -16,6 +16,8 @@
package android.database;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.BytesLong;
import android.annotation.IntRange;
import android.compat.annotation.UnsupportedAppUsage;
@@ -640,6 +642,7 @@
*/
public boolean putBlob(byte[] value,
@IntRange(from = 0) int row, @IntRange(from = 0) int column) {
+ requireNonNull(value);
acquireReference();
try {
return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
@@ -658,6 +661,7 @@
*/
public boolean putString(String value,
@IntRange(from = 0) int row, @IntRange(from = 0) int column) {
+ requireNonNull(value);
acquireReference();
try {
return nativePutString(mWindowPtr, value, row - mStartPos, column);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index cbac912..ca3e3d2 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -569,7 +569,6 @@
return native_setup(
new WeakReference<>(this),
cameraId,
- ActivityThread.currentOpPackageName(),
rotationOverride,
forceSlowJpegMode,
clientAttribution.getParcel(),
@@ -660,7 +659,6 @@
private native int native_setup(
Object cameraThis,
int cameraId,
- String packageName,
int rotationOverride,
boolean forceSlowJpegMode,
Parcel clientAttributionParcel,
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 2dbd4b8..6201359 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -980,6 +980,8 @@
clientAttribution.uid = USE_CALLING_UID;
clientAttribution.pid = USE_CALLING_PID;
clientAttribution.deviceId = contextAttribution.deviceId;
+ clientAttribution.packageName = mContext.getOpPackageName();
+ clientAttribution.attributionTag = mContext.getAttributionTag();
clientAttribution.next = new AttributionSourceState[0];
return clientAttribution;
}
@@ -1041,8 +1043,6 @@
cameraService.connectDevice(
callbacks,
cameraId,
- mContext.getOpPackageName(),
- mContext.getAttributionTag(),
oomScoreOffset,
mContext.getApplicationInfo().targetSdkVersion,
rotationOverride,
diff --git a/core/java/android/util/SequenceUtils.java b/core/java/android/util/SequenceUtils.java
index f833ce3..4f8db0f 100644
--- a/core/java/android/util/SequenceUtils.java
+++ b/core/java/android/util/SequenceUtils.java
@@ -25,8 +25,8 @@
* {@link #getInitSeq}.
* 2. Whenever a newer info needs to be sent to the client side, the system server should first
* update its seq with {@link #getNextSeq}, then send the new info with the new seq to the client.
- * 3. On the client side, when receiving a new info, it should only consume it if it is newer than
- * the last received info seq by checking {@link #isIncomingSeqNewer}.
+ * 3. On the client side, when receiving a new info, it should only consume it if it is not stale by
+ * checking {@link #isIncomingSeqStale}.
*
* @hide
*/
@@ -36,15 +36,22 @@
}
/**
- * Returns {@code true} if the incomingSeq is newer than the curSeq.
+ * Returns {@code true} if the incomingSeq is stale, which means the client should not consume
+ * it.
*/
- public static boolean isIncomingSeqNewer(int curSeq, int incomingSeq) {
+ public static boolean isIncomingSeqStale(int curSeq, int incomingSeq) {
+ if (curSeq == getInitSeq()) {
+ // curSeq can be set to the initial seq in the following cases:
+ // 1. The client process/field is newly created/recreated.
+ // 2. The field is not managed by the system server, such as WindowlessWindowManager.
+ // The client should always consume the incoming in these cases.
+ return false;
+ }
// Convert to long for comparison.
final long diff = (long) incomingSeq - curSeq;
- // If there has been a sufficiently large jump, assume the sequence has wrapped around.
- // For example, when the last seq is MAX_VALUE, the incoming seq will be MIN_VALUE + 1.
- // diff = MIN_VALUE + 1 - MAX_VALUE. It is smaller than 0, but should be treated as newer.
- return diff > 0 || diff < Integer.MIN_VALUE;
+ // When diff is 0, allow client to consume.
+ // When there has been a sufficiently large jump, assume the sequence has wrapped around.
+ return (diff < 0 && diff > Integer.MIN_VALUE) || diff > Integer.MAX_VALUE;
}
/** Returns the initial seq. */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4766942..f77e219 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5512,6 +5512,14 @@
public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 1 << 13;
/**
+ * Flag indicating that this drag will result in the caller activity's task to be hidden for the
+ * duration of the drag, this means that the source activity will not receive drag events for
+ * the current drag gesture. Only the current voice interaction service may use this flag.
+ * @hide
+ */
+ public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14;
+
+ /**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
*/
private float mVerticalScrollFactor;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 88dc3f4..cdf2788 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,7 +23,7 @@
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.util.SequenceUtils.getInitSeq;
-import static android.util.SequenceUtils.isIncomingSeqNewer;
+import static android.util.SequenceUtils.isIncomingSeqStale;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
@@ -2329,23 +2329,23 @@
if (mLastReportedFrames == null) {
return;
}
- if (isIncomingSeqNewer(mLastReportedFrames.seq, inOutFrames.seq)) {
+ if (isIncomingSeqStale(mLastReportedFrames.seq, inOutFrames.seq)) {
+ // If the incoming is stale, use the last reported instead.
+ inOutFrames.setTo(mLastReportedFrames);
+ } else {
// Keep track of the latest.
mLastReportedFrames.setTo(inOutFrames);
- } else {
- // If the last reported frames is newer, use the last reported instead.
- inOutFrames.setTo(mLastReportedFrames);
}
}
private void onInsetsStateChanged(@NonNull InsetsState insetsState) {
if (insetsControlSeq()) {
- if (isIncomingSeqNewer(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
- mLastReportedInsetsStateSeq = insetsState.getSeq();
- } else {
- // The last reported InsetsState is newer. Skip.
+ if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
+ // The incoming is stale. Skip.
return;
}
+ // Keep track of the latest.
+ mLastReportedInsetsStateSeq = insetsState.getSeq();
}
if (mTranslator != null) {
@@ -2362,13 +2362,13 @@
}
if (insetsControlSeq()) {
- if (isIncomingSeqNewer(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
- mLastReportedActiveControlsSeq = activeControls.getSeq();
- } else {
- // The last reported controls is newer. Skip.
+ if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
+ // The incoming is stale. Skip.
activeControls.release();
return;
}
+ // Keep track of the latest.
+ mLastReportedActiveControlsSeq = activeControls.getSeq();
}
final InsetsSourceControl[] controls = activeControls.get();
@@ -7530,7 +7530,6 @@
animationCallback.onBackCancelled();
} else {
topCallback.onBackInvoked();
- return FINISH_HANDLED;
}
break;
}
@@ -7538,14 +7537,16 @@
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
if (!keyEvent.isCanceled()) {
topCallback.onBackInvoked();
- return FINISH_HANDLED;
} else {
Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true");
}
}
}
-
- return FINISH_NOT_HANDLED;
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ // forward a cancelled event so that following stages cancel their back logic
+ keyEvent.cancel();
+ }
+ return FORWARD;
}
@Override
diff --git a/core/java/com/android/internal/os/BinderTransactionNameResolver.java b/core/java/com/android/internal/os/BinderTransactionNameResolver.java
index 5f6f427..f1dc1f2 100644
--- a/core/java/com/android/internal/os/BinderTransactionNameResolver.java
+++ b/core/java/com/android/internal/os/BinderTransactionNameResolver.java
@@ -17,6 +17,7 @@
package com.android.internal.os;
import android.os.Binder;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -37,6 +38,8 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class BinderTransactionNameResolver {
private static final Method NO_GET_DEFAULT_TRANSACTION_NAME_METHOD;
+ private static final boolean USE_TRANSACTION_CODES_FOR_UNKNOWN_METHODS =
+ Flags.useTransactionCodesForUnknownMethods();
/**
* Generates the default transaction method name, which is just the transaction code.
@@ -81,7 +84,14 @@
}
try {
- return (String) method.invoke(null, transactionCode);
+ String methodName = (String) method.invoke(null, transactionCode);
+ if (USE_TRANSACTION_CODES_FOR_UNKNOWN_METHODS) {
+ return TextUtils.isEmpty(methodName)
+ ? String.valueOf(transactionCode)
+ : methodName;
+ } else {
+ return methodName;
+ }
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index c8d6810..30fa4f1 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -9,3 +9,14 @@
is_fixed_read_only: true
bug: "241474956"
}
+
+flag {
+ name: "use_transaction_codes_for_unknown_methods"
+ namespace: "dropbox"
+ description: "Use transaction codes when the method names is unknown"
+ bug: "350041302"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 95b6146..d244874 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -48,7 +48,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -66,7 +65,7 @@
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
- private final Map<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final Runnable mCacheUpdater;
private final int mPerChunkSize;
@@ -75,19 +74,20 @@
private final Object mProtoLogEnabledLock = new Object();
public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
- Runnable cacheUpdater) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, cacheUpdater);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups, cacheUpdater);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
- Runnable cacheUpdater) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
+ mLogGroups = logGroups;
mCacheUpdater = cacheUpdater;
}
@@ -97,25 +97,21 @@
@VisibleForTesting
@Override
public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args) {
+ @Nullable String messageString, Object[] args) {
if (group.isLogToProto()) {
logToProto(messageHash, paramsMask, args);
}
if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), level, messageHash, args);
+ logToLogcat(group.getTag(), level, messageHash, messageString, args);
}
}
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
- // This will be removed very soon so no point implementing it here.
- throw new IllegalStateException(
- "Not implemented. Only implemented for PerfettoProtoLogImpl.");
- }
-
- private void logToLogcat(String tag, LogLevel level, long messageHash, Object[] args) {
+ private void logToLogcat(String tag, LogLevel level, long messageHash,
+ @Nullable String messageString, Object[] args) {
String message = null;
- final String messageString = mViewerConfig.getViewerString(messageHash);
+ if (messageString == null) {
+ messageString = mViewerConfig.getViewerString(messageHash);
+ }
if (messageString != null) {
if (args != null) {
try {
@@ -414,12 +410,5 @@
// so we ignore the level argument to this function.
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- for (IProtoLogGroup group : protoLogGroups) {
- mLogGroups.put(group.name(), group);
- }
- }
}
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
deleted file mode 100644
index 37d09c2..0000000
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2024 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.protolog;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.protolog.common.ILogger;
-import com.android.internal.protolog.common.IProtoLog;
-import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.internal.protolog.common.LogLevel;
-
-/**
- * Class only create and used to server temporarily for when there is source code pre-processing by
- * the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
- * boolean is false. In which case we simply want to log protolog message to logcat. Note, that this
- * means that in such cases there is no real advantage of using protolog over logcat.
- *
- * @deprecated Should not be used. This is just a temporary class to support a legacy behavior.
- */
-@Deprecated
-public class LogcatOnlyProtoLogImpl implements IProtoLog {
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args) {
- throw new RuntimeException("Not supported when using LogcatOnlyProtoLogImpl");
- }
-
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
- String formattedString = TextUtils.formatSimple(messageString, args);
- switch (logLevel) {
- case VERBOSE -> Log.v(group.getTag(), formattedString);
- case INFO -> Log.i(group.getTag(), formattedString);
- case DEBUG -> Log.d(group.getTag(), formattedString);
- case WARN -> Log.w(group.getTag(), formattedString);
- case ERROR -> Log.e(group.getTag(), formattedString);
- case WTF -> Log.wtf(group.getTag(), formattedString);
- }
- }
-
- @Override
- public boolean isProtoEnabled() {
- return false;
- }
-
- @Override
- public int startLoggingToLogcat(String[] groups, ILogger logger) {
- return 0;
- }
-
- @Override
- public int stopLoggingToLogcat(String[] groups, ILogger logger) {
- return 0;
- }
-
- @Override
- public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- return true;
- }
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- // Does nothing
- }
-}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 5f6766e..42fa6ac 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -42,7 +42,6 @@
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_NEEDS_INCREMENTAL_STATE;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
import android.os.ShellCommand;
@@ -73,45 +72,37 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
/**
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
private static final String LOG_TAG = "ProtoLog";
- public static final String NULL_STRING = "null";
private final AtomicInteger mTracingInstances = new AtomicInteger();
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
this::onTracingInstanceStart,
- this::onTracingFlush,
+ this::dumpTransitionTraceConfig,
this::onTracingInstanceStop
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final Runnable mCacheUpdater;
- private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
- private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
- private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
+ private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
+ private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
- private final Lock mBackgroundServiceLock = new ReentrantLock();
- private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(String viewerConfigFilePath, Runnable cacheUpdater) {
+ public PerfettoProtoLogImpl(String viewerConfigFilePath,
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -119,19 +110,16 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- }, cacheUpdater);
- }
-
- public PerfettoProtoLogImpl() {
- this(null, null, () -> {});
+ }, logGroups, cacheUpdater);
}
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups,
cacheUpdater);
}
@@ -139,6 +127,7 @@
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
ProtoLogViewerConfigReader viewerConfigReader,
+ TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
@@ -151,6 +140,7 @@
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
+ this.mLogGroups = logGroups;
this.mCacheUpdater = cacheUpdater;
}
@@ -159,70 +149,23 @@
*/
@VisibleForTesting
@Override
- public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable Object[] args) {
- log(logLevel, group, new Message(messageHash, paramsMask), args);
- }
+ public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString, Object[] args) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "log");
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
- log(logLevel, group, new Message(messageString), args);
- }
-
- private void log(LogLevel logLevel, IProtoLogGroup group, Message message,
- @Nullable Object[] args) {
- if (isProtoEnabled()) {
- long tsNanos = SystemClock.elapsedRealtimeNanos();
- final String stacktrace;
- if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
- stacktrace = collectStackTrace();
- } else {
- stacktrace = null;
- }
- try {
- mBackgroundServiceLock.lock();
- mBackgroundLoggingService.execute(() ->
- logToProto(logLevel, group, message, args, tsNanos,
- stacktrace));
- } finally {
- mBackgroundServiceLock.unlock();
- }
- }
- if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), logLevel, message, args);
- }
- }
-
- private void onTracingFlush() {
- final ExecutorService loggingService;
+ long tsNanos = SystemClock.elapsedRealtimeNanos();
try {
- mBackgroundServiceLock.lock();
- loggingService = mBackgroundLoggingService;
- mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ mBackgroundLoggingService.submit(() ->
+ logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
+ if (group.isLogToLogcat()) {
+ logToLogcat(group.getTag(), level, messageHash, messageString, args);
+ }
} finally {
- mBackgroundServiceLock.unlock();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
-
- try {
- loggingService.shutdown();
- boolean finished = loggingService.awaitTermination(10, TimeUnit.SECONDS);
-
- if (!finished) {
- Log.e(LOG_TAG, "ProtoLog background tracing service didn't finish gracefully.");
- }
- } catch (InterruptedException e) {
- Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
- }
-
- dumpTransitionTraceConfig();
}
private void dumpTransitionTraceConfig() {
- if (mViewerConfigInputStreamProvider == null) {
- // No viewer config available
- return;
- }
-
ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
if (pis == null) {
@@ -313,53 +256,39 @@
os.end(outMessagesToken);
}
- private void logToLogcat(String tag, LogLevel level, Message message,
- @Nullable Object[] args) {
- String messageString = message.getMessage(mViewerConfigReader);
-
- if (messageString == null) {
- StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
- if (args != null) {
- builder.append(" args = (");
- builder.append(String.join(", ", Arrays.stream(args)
- .map(it -> {
- if (it == null) {
- return "null";
- } else {
- return it.toString();
- }
- }).toList()));
- builder.append(")");
- }
- messageString = builder.toString();
- args = new Object[0];
- }
-
- logToLogcat(tag, level, messageString, args);
- }
-
- private void logToLogcat(String tag, LogLevel level, String message, @Nullable Object[] args) {
+ private void logToLogcat(String tag, LogLevel level, long messageHash,
+ @Nullable String messageString, Object[] args) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToLogcat");
try {
- doLogToLogcat(tag, level, message, args);
+ doLogToLogcat(tag, level, messageHash, messageString, args);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void doLogToLogcat(String tag, LogLevel level, @NonNull String messageString,
- @Nullable Object[] args) {
- String message;
- if (args != null) {
- try {
- message = TextUtils.formatSimple(messageString, args);
- } catch (IllegalArgumentException e) {
- message = "FORMAT_ERROR \"" + messageString + "\", args=("
- + String.join(
- ", ", Arrays.stream(args).map(Object::toString).toList()) + ")";
+ private void doLogToLogcat(String tag, LogLevel level, long messageHash,
+ @androidx.annotation.Nullable String messageString, Object[] args) {
+ String message = null;
+ if (messageString == null) {
+ messageString = mViewerConfigReader.getViewerString(messageHash);
+ }
+ if (messageString != null) {
+ if (args != null) {
+ try {
+ message = TextUtils.formatSimple(messageString, args);
+ } catch (Exception ex) {
+ Slog.w(LOG_TAG, "Invalid ProtoLog format string.", ex);
+ }
+ } else {
+ message = messageString;
}
- } else {
- message = messageString;
+ }
+ if (message == null) {
+ StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+ for (Object o : args) {
+ builder.append(" ").append(o);
+ }
+ message = builder.toString();
}
passToLogcat(tag, level, message);
}
@@ -391,21 +320,25 @@
}
}
- private void logToProto(LogLevel level, IProtoLogGroup logGroup, Message message, Object[] args,
- long tsNanos, @Nullable String stacktrace) {
+ private void logToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
+ Object[] args, long tsNanos) {
+ if (!isProtoEnabled()) {
+ return;
+ }
+
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToProto");
try {
- doLogToProto(level, logGroup, message, args, tsNanos, stacktrace);
+ doLogToProto(level, groupName, messageHash, paramsMask, args, tsNanos);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void doLogToProto(LogLevel level, IProtoLogGroup logGroup, Message message,
- Object[] args, long tsNanos, @Nullable String stacktrace) {
+ private void doLogToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
+ Object[] args, long tsNanos) {
mDataSource.trace(ctx -> {
final ProtoLogDataSource.TlsState tlsState = ctx.getCustomTlsState();
- final LogLevel logFrom = tlsState.getLogFromLevel(logGroup.name());
+ final LogLevel logFrom = tlsState.getLogFromLevel(groupName);
if (level.ordinal() < logFrom.ordinal()) {
return;
@@ -417,44 +350,30 @@
// trace processing easier.
int argIndex = 0;
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
+ int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
if (type == LogDataType.STRING) {
- if (o == null) {
- internStringArg(ctx, NULL_STRING);
- } else {
- internStringArg(ctx, o.toString());
- }
+ internStringArg(ctx, o.toString());
}
argIndex++;
}
}
int internedStacktrace = 0;
- if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
+ if (tlsState.getShouldCollectStacktrace(groupName)) {
// Intern stackstraces before creating the trace packet for the proto message so
// that the interned stacktrace strings appear before in the trace to make the
// trace processing easier.
+ String stacktrace = collectStackTrace();
internedStacktrace = internStacktraceString(ctx, stacktrace);
}
- boolean needsIncrementalState = false;
-
- long messageHash = 0;
- if (message.mMessageHash != null) {
- messageHash = message.mMessageHash;
- }
- if (message.mMessageString != null) {
- needsIncrementalState = true;
- messageHash =
- internProtoMessage(ctx, level, logGroup, message.mMessageString);
- }
-
final ProtoOutputStream os = ctx.newTracePacket();
os.write(TIMESTAMP, tsNanos);
long token = os.start(PROTOLOG_MESSAGE);
-
os.write(MESSAGE_ID, messageHash);
+ boolean needsIncrementalState = false;
+
if (args != null) {
int argIndex = 0;
@@ -462,39 +381,22 @@
ArrayList<Double> doubleParams = new ArrayList<>();
ArrayList<Boolean> booleanParams = new ArrayList<>();
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
+ int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
try {
switch (type) {
case LogDataType.STRING:
- final int internedStringId;
- if (o == null) {
- internedStringId = internStringArg(ctx, NULL_STRING);
- } else {
- internedStringId = internStringArg(ctx, o.toString());
- }
+ final int internedStringId = internStringArg(ctx, o.toString());
os.write(STR_PARAM_IIDS, internedStringId);
needsIncrementalState = true;
break;
case LogDataType.LONG:
- if (o == null) {
- longParams.add(0);
- } else {
- longParams.add(((Number) o).longValue());
- }
+ longParams.add(((Number) o).longValue());
break;
case LogDataType.DOUBLE:
- if (o == null) {
- doubleParams.add(0d);
- } else {
- doubleParams.add(((Number) o).doubleValue());
- }
+ doubleParams.add(((Number) o).doubleValue());
break;
case LogDataType.BOOLEAN:
- if (o == null) {
- booleanParams.add(false);
- } else {
- booleanParams.add((boolean) o);
- }
+ booleanParams.add((boolean) o);
break;
}
} catch (ClassCastException ex) {
@@ -512,7 +414,7 @@
booleanParams.forEach(it -> os.write(BOOLEAN_PARAMS, it ? 1 : 0));
}
- if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
+ if (tlsState.getShouldCollectStacktrace(groupName)) {
os.write(STACKTRACE_IID, internedStacktrace);
}
@@ -525,63 +427,6 @@
});
}
- private long internProtoMessage(
- TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState,
- ProtoLogDataSource.IncrementalState> ctx, LogLevel level,
- IProtoLogGroup logGroup, String message) {
- final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
-
- if (!incrementalState.clearReported) {
- final ProtoOutputStream os = ctx.newTracePacket();
- os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED);
- incrementalState.clearReported = true;
- }
-
-
- if (!incrementalState.protologGroupInterningSet.contains(logGroup.getId())) {
- incrementalState.protologGroupInterningSet.add(logGroup.getId());
-
- final ProtoOutputStream os = ctx.newTracePacket();
- final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
- final long groupConfigToken = os.start(GROUPS);
-
- os.write(ID, logGroup.getId());
- os.write(NAME, logGroup.name());
- os.write(TAG, logGroup.getTag());
-
- os.end(groupConfigToken);
- os.end(protologViewerConfigToken);
- }
-
- final Long messageHash = hash(level, logGroup.name(), message);
- if (!incrementalState.protologMessageInterningSet.contains(messageHash)) {
- incrementalState.protologMessageInterningSet.add(messageHash);
-
- final ProtoOutputStream os = ctx.newTracePacket();
- final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
- final long messageConfigToken = os.start(MESSAGES);
-
- os.write(MessageData.MESSAGE_ID, messageHash);
- os.write(MESSAGE, message);
- os.write(LEVEL, level.ordinal());
- os.write(GROUP_ID, logGroup.getId());
-
- os.end(messageConfigToken);
- os.end(protologViewerConfigToken);
- }
-
- return messageHash;
- }
-
- private Long hash(
- LogLevel logLevel,
- String logGroup,
- String messageString
- ) {
- final String fullStringIdentifier = messageString + logLevel + logGroup;
- return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
- }
-
private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
private String collectStackTrace() {
@@ -621,7 +466,7 @@
ProtoLogDataSource.IncrementalState> ctx,
Map<String, Integer> internMap,
long fieldId,
- @NonNull String string
+ String string
) {
final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -678,17 +523,25 @@
@Override
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- final int[] groupLevelCount = mLogLevelCounts.get(group);
- return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
- || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
- || group.isLogToLogcat();
+ return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal();
}
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- for (IProtoLogGroup protoLogGroup : protoLogGroups) {
- mLogGroups.put(protoLogGroup.name(), protoLogGroup);
+ private LogLevel getLogFromLevel(IProtoLogGroup group) {
+ if (mLogLevelCounts.containsKey(group)) {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ } else {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
}
+
+ return LogLevel.WTF;
}
/**
@@ -767,51 +620,36 @@
}
private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.incrementAndGet();
+
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- mDefaultLogLevelCounts[i]++;
- }
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1);
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- if (group == null) {
- throw new IllegalArgumentException("Trying to set config for \""
- + overriddenGroupTag + "\" that isn't registered");
- }
-
- mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
- logLevelsCountsForGroup[i]++;
- }
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
- }
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
- }
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1);
}
mCacheUpdater.run();
-
- this.mTracingInstances.incrementAndGet();
}
private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
this.mTracingInstances.decrementAndGet();
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- mDefaultLogLevelCounts[i]--;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.get(defaultLogFrom) - 1);
+ if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) {
+ mDefaultLogLevelCounts.remove(defaultLogFrom);
}
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
@@ -819,24 +657,18 @@
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- logLevelsCountsForGroup[i]--;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.get(logFromLevel) - 1);
+ if (logLevelsCountsForGroup.get(logFromLevel) <= 0) {
+ logLevelsCountsForGroup.remove(logFromLevel);
}
- if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
+ if (logLevelsCountsForGroup.isEmpty()) {
mLogLevelCounts.remove(group);
}
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.get(group) - 1);
-
- if (mCollectStackTraceGroupCounts.get(group) == 0) {
- mCollectStackTraceGroupCounts.remove(group);
- }
- }
}
mCacheUpdater.run();
@@ -849,36 +681,5 @@
pw.flush();
}
}
-
- private static class Message {
- private final Long mMessageHash;
- private final Integer mMessageMask;
- private final String mMessageString;
-
- private Message(Long messageHash, int messageMask) {
- this.mMessageHash = messageHash;
- this.mMessageMask = messageMask;
- this.mMessageString = null;
- }
-
- private Message(String messageString) {
- this.mMessageHash = null;
- final List<Integer> argTypes = LogDataType.parseFormatString(messageString);
- this.mMessageMask = LogDataType.logDataTypesToBitMask(argTypes);
- this.mMessageString = messageString;
- }
-
- private int getMessageMask() {
- return mMessageMask;
- }
-
- private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) {
- if (mMessageString != null) {
- return mMessageString;
- }
-
- return viewerConfigReader.getViewerString(mMessageHash);
- }
- }
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 99d4418..0118c05 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -44,23 +44,21 @@
// LINT.ThenChange(frameworks/base/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt)
// Needs to be set directly otherwise the protologtool tries to transform the method call
- @Deprecated
public static boolean REQUIRE_PROTOLOGTOOL = true;
- private static IProtoLog sProtoLogInstance;
-
/**
* DEBUG level log.
*
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void d(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.DEBUG, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -69,12 +67,13 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void v(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.VERBOSE, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -83,12 +82,13 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void i(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.INFO, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -97,12 +97,13 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void w(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.WARN, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -111,12 +112,13 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void e(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.ERROR, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -125,12 +127,13 @@
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.WTF, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -139,7 +142,11 @@
* @return true iff this is being logged.
*/
public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- return sProtoLogInstance.isEnabled(group, level);
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+ return false;
}
/**
@@ -147,38 +154,10 @@
* @return A singleton instance of ProtoLog.
*/
public static IProtoLog getSingleInstance() {
- return sProtoLogInstance;
- }
-
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- sProtoLogInstance.registerGroups(protoLogGroups);
- }
-
- private static void logStringMessage(LogLevel logLevel, IProtoLogGroup group,
- String stringMessage, Object... args) {
- if (sProtoLogInstance == null) {
- throw new IllegalStateException(
- "Trying to use ProtoLog before it is initialized in this process.");
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
}
-
- if (sProtoLogInstance.isEnabled(group, logLevel)) {
- sProtoLogInstance.log(logLevel, group, stringMessage, args);
- }
- }
-
- static {
- if (android.tracing.Flags.perfettoProtologTracing()) {
- sProtoLogInstance = new PerfettoProtoLogImpl();
- } else {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new RuntimeException("REQUIRE_PROTOLOGTOOL not set to false.");
- } else {
- sProtoLogInstance = new LogcatOnlyProtoLogImpl();
- }
- }
+ return null;
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 84f3237..6ab79b9 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -40,7 +40,6 @@
import java.io.IOException;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -139,8 +138,6 @@
}
public static class IncrementalState {
- public final Set<Integer> protologGroupInterningSet = new HashSet<>();
- public final Set<Long> protologMessageInterningSet = new HashSet<>();
public final Map<String, Integer> argumentInterningMap = new HashMap<>();
public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
public boolean clearReported = false;
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 3082295..6d142af 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -54,33 +54,48 @@
private static Runnable sCacheUpdater;
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void d(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.DEBUG, group, messageHash, paramsMask, args);
+ public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void v(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, args);
+ public static void v(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+ args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void i(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, args);
+ public static void i(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void w(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, args);
+ public static void w(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void e(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.ERROR, group, messageHash, paramsMask, args);
+ public static void e(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, args);
+ public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
}
/**
@@ -92,27 +107,18 @@
}
/**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- getSingleInstance().registerGroups(protoLogGroups);
- }
-
- /**
* Returns the single instance of the ProtoLogImpl singleton class.
*/
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+ sServiceInstance = new PerfettoProtoLogImpl(
+ sViewerConfigPath, sLogGroups, sCacheUpdater);
} else {
sServiceInstance = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups, sCacheUpdater);
}
- IProtoLogGroup[] groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
- sServiceInstance.registerGroups(groups);
sCacheUpdater.run();
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index f5695ac..f72d9f7 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -27,19 +27,11 @@
* @param group The group this message belongs to.
* @param messageHash The hash of the message.
* @param paramsMask The parameters mask of the message.
- * @param args The arguments of the message.
- */
- void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args);
-
- /**
- * Log a ProtoLog message
- * @param logLevel Log level of the proto message.
- * @param group The group this message belongs to.
* @param messageString The message string.
* @param args The arguments of the message.
*/
- void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args);
+ void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+ String messageString, Object[] args);
/**
* Check if ProtoLog is tracing.
@@ -68,10 +60,4 @@
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
-
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- void registerGroups(IProtoLogGroup... protoLogGroups);
}
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index f5f04a7..98e6e85 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -35,8 +35,10 @@
import android.view.RemotableViewMethod;
import android.widget.RemoteViews;
+import com.android.internal.R;
+
/**
- * An image view that holds the icon displayed on the left side of a notification row.
+ * An image view that holds the icon displayed at the start of a notification row.
*/
@RemoteViews.RemoteView
public class NotificationRowIconView extends CachingIconView {
@@ -98,9 +100,12 @@
setPadding(0, 0, 0, 0);
// Make the background white in case the icon itself doesn't have one.
- int white = Color.rgb(255, 255, 255);
- ColorFilter colorFilter = new PorterDuffColorFilter(white,
+ ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE,
PorterDuff.Mode.SRC_ATOP);
+
+ if (mOriginalBackground == null) {
+ setBackground(getContext().getDrawable(R.drawable.notification_icon_circle));
+ }
getBackground().mutate().setColorFilter(colorFilter);
} else {
// Restore original padding and background if needed
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index b8fd3d0..3f74fac 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -582,8 +582,8 @@
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jint cameraId, jstring clientPackageName,
- jint rotationOverride, jboolean forceSlowJpegMode,
+ jint cameraId, jint rotationOverride,
+ jboolean forceSlowJpegMode,
jobject jClientAttributionParcel,
jint devicePolicy) {
AttributionSourceState clientAttribution;
@@ -591,16 +591,8 @@
return -EACCES;
}
- // Convert jstring to String16
- const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
- env->GetStringChars(clientPackageName, NULL));
- jsize rawClientNameLen = env->GetStringLength(clientPackageName);
- std::string clientName = toStdString(rawClientName, rawClientNameLen);
- env->ReleaseStringChars(clientPackageName,
- reinterpret_cast<const jchar*>(rawClientName));
-
int targetSdkVersion = android_get_application_target_sdk_version();
- sp<Camera> camera = Camera::connect(cameraId, clientName, targetSdkVersion, rotationOverride,
+ sp<Camera> camera = Camera::connect(cameraId, targetSdkVersion, rotationOverride,
forceSlowJpegMode, clientAttribution, devicePolicy);
if (camera == NULL) {
return -EACCES;
@@ -1089,7 +1081,7 @@
(void *)android_hardware_Camera_getNumberOfCameras},
{"_getCameraInfo", "(IILandroid/os/Parcel;ILandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZLandroid/os/Parcel;I)I",
+ {"native_setup", "(Ljava/lang/Object;IIZLandroid/os/Parcel;I)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
index 0438dc5..b45483b 100644
--- a/core/res/res/layout/notification_template_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -77,7 +77,7 @@
android:scaleType="center"
/>
- <com.android.internal.widget.CachingIconView
+ <com.android.internal.widget.NotificationRowIconView
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c9b5d41..419a615 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4059,6 +4059,7 @@
<java-symbol type="id" name="snooze_button" />
<java-symbol type="dimen" name="text_size_body_2_material" />
<java-symbol type="dimen" name="notification_icon_circle_size" />
+ <java-symbol type="drawable" name="notification_icon_circle" />
<java-symbol type="dimen" name="messaging_avatar_size" />
<java-symbol type="dimen" name="messaging_group_sending_progress_size" />
<java-symbol type="dimen" name="messaging_image_rounding" />
diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
index 020520d..6ca1751 100644
--- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java
+++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
@@ -19,7 +19,7 @@
import static android.util.SequenceUtils.getInitSeq;
import static android.util.SequenceUtils.getNextSeq;
-import static android.util.SequenceUtils.isIncomingSeqNewer;
+import static android.util.SequenceUtils.isIncomingSeqStale;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,30 +60,35 @@
}
@Test
- public void testIsIncomingSeqNewer() {
- assertTrue(isIncomingSeqNewer(getInitSeq() + 1, getInitSeq() + 10));
- assertFalse(isIncomingSeqNewer(getInitSeq() + 10, getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(-100, 100));
- assertFalse(isIncomingSeqNewer(100, -100));
- assertTrue(isIncomingSeqNewer(1, 2));
- assertFalse(isIncomingSeqNewer(2, 1));
+ public void testIsIncomingSeqStale() {
+ assertFalse(isIncomingSeqStale(getInitSeq() + 1, getInitSeq() + 10));
+ assertTrue(isIncomingSeqStale(getInitSeq() + 10, getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(-100, 100));
+ assertTrue(isIncomingSeqStale(100, -100));
+ assertFalse(isIncomingSeqStale(1, 2));
+ assertTrue(isIncomingSeqStale(2, 1));
// Possible incoming seq are all newer than the initial seq.
- assertTrue(isIncomingSeqNewer(getInitSeq(), getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(getInitSeq(), -100));
- assertTrue(isIncomingSeqNewer(getInitSeq(), 0));
- assertTrue(isIncomingSeqNewer(getInitSeq(), 100));
- assertTrue(isIncomingSeqNewer(getInitSeq(), Integer.MAX_VALUE));
- assertTrue(isIncomingSeqNewer(getInitSeq(), getNextSeq(Integer.MAX_VALUE)));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getInitSeq()));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(getInitSeq(), -100));
+ assertFalse(isIncomingSeqStale(getInitSeq(), 0));
+ assertFalse(isIncomingSeqStale(getInitSeq(), 100));
+ assertFalse(isIncomingSeqStale(getInitSeq(), Integer.MAX_VALUE));
+ assertFalse(isIncomingSeqStale(getInitSeq(), getNextSeq(Integer.MAX_VALUE)));
// False for the same seq.
- assertFalse(isIncomingSeqNewer(getInitSeq(), getInitSeq()));
- assertFalse(isIncomingSeqNewer(100, 100));
- assertFalse(isIncomingSeqNewer(Integer.MAX_VALUE, Integer.MAX_VALUE));
+ assertFalse(isIncomingSeqStale(100, 100));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, Integer.MAX_VALUE));
- // True when there is a large jump (overflow).
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 1));
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getInitSeq() + 100));
- assertTrue(isIncomingSeqNewer(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE)));
+ // False when there is a large jump (overflow).
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getInitSeq() + 1));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getInitSeq() + 100));
+ assertFalse(isIncomingSeqStale(Integer.MAX_VALUE, getNextSeq(Integer.MAX_VALUE)));
+
+ // True when the large jump is opposite (curSeq is newer).
+ assertTrue(isIncomingSeqStale(getInitSeq() + 1, Integer.MAX_VALUE));
+ assertTrue(isIncomingSeqStale(getInitSeq() + 100, Integer.MAX_VALUE));
+ assertTrue(isIncomingSeqStale(getNextSeq(Integer.MAX_VALUE), Integer.MAX_VALUE));
}
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 9337bf6..033ac7c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.util.SequenceUtils.getInitSeq;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -1555,9 +1556,9 @@
final InsetsState state0 = new InsetsState();
final InsetsState state1 = new InsetsState();
state0.setDisplayFrame(new Rect(0, 0, 500, 1000));
- state0.setSeq(10000);
+ state0.setSeq(getInitSeq() + 10000);
state1.setDisplayFrame(new Rect(0, 0, 1500, 2000));
- state1.setSeq(10001);
+ state1.setSeq(getInitSeq() + 10001);
final InsetsSourceControl.Array array = new InsetsSourceControl.Array();
sInstrumentation.runOnMainSync(() -> {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 822a07c..544f0f3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -894,9 +894,7 @@
private static boolean isDraggingToFullscreenAllowed(
@NonNull DividerAttributes dividerAttributes) {
- // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
- // updated to v7.
- return false;
+ return dividerAttributes.isDraggingToFullscreenAllowed();
}
/**
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index ebebd8a..cb422ea 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,5 +1,5 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 61ec49c..8c2cfd1e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -20,7 +20,6 @@
import android.provider.Settings
import android.util.Log
import com.android.window.flags.Flags
-import com.android.wm.shell.shared.DesktopModeStatus
/*
* A shared class to check desktop mode flags state.
@@ -35,13 +34,9 @@
private val shouldOverrideByDevOption: Boolean
) {
// All desktop mode related flags will be added here
- DESKTOP_WINDOWING_MODE(DesktopModeStatus::isDesktopModeFlagEnabled, true),
+ DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true);
- // Local cache for toggle override, which is initialized once on its first access. It needs to be
- // refreshed only on reboots as overridden state takes effect on reboots.
- private var cachedToggleOverride: ToggleOverride? = null
-
/**
* Determines state of flag based on the actual flag and desktop mode developer option overrides.
*
@@ -139,13 +134,19 @@
}
}
- private companion object {
- const val TAG = "DesktopModeFlags"
+ companion object {
+ private const val TAG = "DesktopModeFlags"
/**
* Key for non-persistent System Property which is used to store desktop windowing developer
* option overrides.
*/
- const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+ private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override"
+
+ /**
+ * Local cache for toggle override, which is initialized once on its first access. It needs to
+ * be refreshed only on reboots as overridden state takes effect on reboots.
+ */
+ private var cachedToggleOverride: ToggleOverride? = null
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
similarity index 93%
rename from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 67d46f4..fc4710f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.shared;
+package com.android.wm.shell.shared.desktopmode;
import android.annotation.NonNull;
import android.content.Context;
@@ -23,7 +23,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
/**
* Constants for desktop mode feature
@@ -102,16 +101,6 @@
"persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT);
/**
- * Return {@code true} if desktop windowing flag is enabled. Only to be used for testing.
- * Callers should use {@link #canEnterDesktopMode(Context)} to query the state of desktop
- * windowing.
- */
- @VisibleForTesting
- public static boolean isDesktopModeFlagEnabled() {
- return Flags.enableDesktopWindowingMode();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -167,7 +156,7 @@
/** Returns if desktop mode dev option should be enabled if there is no user override. */
public static boolean shouldDevOptionBeEnabledByDefault() {
- return isDesktopModeFlagEnabled();
+ return Flags.enableDesktopWindowingMode();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ebdea1b..f014e55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -24,6 +24,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.CAMERA_CONTROL_STATE_UPDATE;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_APPEARED;
+import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_CLICKED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -31,7 +34,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.LocusId;
@@ -57,6 +59,11 @@
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -75,8 +82,7 @@
* Unified task organizer for all components in the shell.
* TODO(b/167582004): may consider consolidating this class and TaskOrganizer
*/
-public class ShellTaskOrganizer extends TaskOrganizer implements
- CompatUIController.CompatUICallback {
+public class ShellTaskOrganizer extends TaskOrganizer {
private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
@@ -194,12 +200,11 @@
* In charge of showing compat UI. Can be {@code null} if the device doesn't support size
* compat or if this isn't the main {@link ShellTaskOrganizer}.
*
- * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController},
- * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be
- * initialized with a {@code null} {@link CompatUIController}.
+ * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIHandler},
+ * Subclasses should be initialized with a {@code null} {@link CompatUIHandler}.
*/
@Nullable
- private final CompatUIController mCompatUI;
+ private final CompatUIHandler mCompatUI;
@NonNull
private final ShellCommandHandler mShellCommandHandler;
@@ -223,7 +228,7 @@
public ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -235,7 +240,7 @@
protected ShellTaskOrganizer(ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
- @Nullable CompatUIController compatUI,
+ @Nullable CompatUIHandler compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
@@ -252,7 +257,21 @@
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
- mCompatUI.setCompatUICallback(this);
+ mCompatUI.setCallback(compatUIEvent -> {
+ switch(compatUIEvent.getEventId()) {
+ case SIZE_COMPAT_RESTART_BUTTON_APPEARED:
+ onSizeCompatRestartButtonAppeared(compatUIEvent.asType());
+ break;
+ case SIZE_COMPAT_RESTART_BUTTON_CLICKED:
+ onSizeCompatRestartButtonClicked(compatUIEvent.asType());
+ break;
+ case CAMERA_CONTROL_STATE_UPDATE:
+ onCameraControlStateUpdated(compatUIEvent.asType());
+ break;
+ default:
+
+ }
+ });
}
registerOrganizer();
}
@@ -680,6 +699,22 @@
}
}
+ /**
+ * Shows/hides the given task surface. Not for general use as changing the task visibility may
+ * conflict with other Transitions. This is currently ONLY used to temporarily hide a task
+ * while a drag is in session.
+ */
+ public void setTaskSurfaceVisibility(int taskId, boolean visible) {
+ synchronized (mLock) {
+ final TaskAppearedInfo info = mTasks.get(taskId);
+ if (info != null) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setVisibility(info.getLeash(), visible);
+ t.apply();
+ }
+ }
+ }
+
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
@@ -727,45 +762,6 @@
}
}
- @Override
- public void onSizeCompatRestartButtonAppeared(int taskId) {
- final TaskAppearedInfo info;
- synchronized (mLock) {
- info = mTasks.get(taskId);
- }
- if (info == null) {
- return;
- }
- logSizeCompatRestartButtonEventReported(info,
- FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
- }
-
- @Override
- public void onSizeCompatRestartButtonClicked(int taskId) {
- final TaskAppearedInfo info;
- synchronized (mLock) {
- info = mTasks.get(taskId);
- }
- if (info == null) {
- return;
- }
- logSizeCompatRestartButtonEventReported(info,
- FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__CLICKED);
- restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
- }
-
- @Override
- public void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state) {
- final TaskAppearedInfo info;
- synchronized (mLock) {
- info = mTasks.get(taskId);
- }
- if (info == null) {
- return;
- }
- updateCameraCompatControlState(info.getTaskInfo().token, state);
- }
-
/** Reparents a child window surface to the task surface. */
public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
SurfaceControl.Transaction t) {
@@ -783,6 +779,50 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
+ @VisibleForTesting
+ void onSizeCompatRestartButtonAppeared(@NonNull SizeCompatRestartButtonAppeared compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
+ final TaskAppearedInfo info;
+ synchronized (mLock) {
+ info = mTasks.get(taskId);
+ }
+ if (info == null) {
+ return;
+ }
+ logSizeCompatRestartButtonEventReported(info,
+ FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
+ }
+
+ @VisibleForTesting
+ void onSizeCompatRestartButtonClicked(@NonNull SizeCompatRestartButtonClicked compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
+ final TaskAppearedInfo info;
+ synchronized (mLock) {
+ info = mTasks.get(taskId);
+ }
+ if (info == null) {
+ return;
+ }
+ logSizeCompatRestartButtonEventReported(info,
+ FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__CLICKED);
+ restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
+ }
+
+ @VisibleForTesting
+ void onCameraControlStateUpdated(@NonNull CameraControlStateUpdated compatUIEvent) {
+ final int taskId = compatUIEvent.getTaskId();
+ final int state = compatUIEvent.getState();
+ final TaskAppearedInfo info;
+ synchronized (mLock) {
+ info = mTasks.get(taskId);
+ }
+ if (info == null) {
+ return;
+ }
+ updateCameraCompatControlState(info.getTaskInfo().token, state);
+ }
+
+
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -810,10 +850,10 @@
// on this Task if there is any.
if (taskListener == null || !taskListener.supportCompatUI()
|| !taskInfo.appCompatTaskInfo.hasCompatUI() || !taskInfo.isVisible) {
- mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, null /* taskListener */));
return;
}
- mCompatUI.onCompatInfoChanged(taskInfo, taskListener);
+ mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, taskListener));
}
private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 3ad60e7..1bc1795 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -492,6 +492,11 @@
return mHideHandle;
}
+ /** Returns true if the divider is currently being physically controlled by the user. */
+ boolean isMoving() {
+ return mMoving;
+ }
+
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index bdef4f4..51f9de8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -652,9 +652,18 @@
.ofInt(from, to)
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+
+ // If the divider is being physically controlled by the user, we use a cool parallax effect
+ // on the task windows. So if this "snap" animation is an extension of a user-controlled
+ // movement, we pass in true here to continue the parallax effect smoothly.
+ boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null
+ && mSplitWindowManager.getDividerView().isMoving();
+
mDividerFlingAnimator.addUpdateListener(
animation -> updateDividerBounds(
- (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */)
+ (int) animation.getAnimatedValue(),
+ isBeingMovedByUser /* shouldUseParallaxEffect */
+ )
);
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 5d121c2..46c1a43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -37,7 +37,6 @@
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -192,7 +191,7 @@
mDividerView.setInteractive(interactive, hideHandle, from);
}
- View getDividerView() {
+ DividerView getDividerView() {
return mDividerView;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 2520c25..c02c9cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -50,6 +49,10 @@
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,17 +74,7 @@
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
- DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
-
- /** Callback for compat UI interaction. */
- public interface CompatUICallback {
- /** Called when the size compat restart button appears. */
- void onSizeCompatRestartButtonAppeared(int taskId);
- /** Called when the size compat restart button is clicked. */
- void onSizeCompatRestartButtonClicked(int taskId);
- /** Called when the camera compat control state is updated. */
- void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state);
- }
+ DisplayImeController.ImePositionProcessor, KeyguardChangeListener, CompatUIHandler {
private static final String TAG = "CompatUIController";
@@ -170,7 +163,7 @@
private final Function<Integer, Integer> mDisappearTimeSupplier;
@Nullable
- private CompatUICallback mCompatUICallback;
+ private Consumer<CompatUIEvent> mCallback;
// Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
// be shown.
@@ -230,20 +223,21 @@
mCompatUIShellCommandHandler.onInit();
}
- /** Sets the callback for Compat UI interactions. */
- public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
- mCompatUICallback = compatUiCallback;
+ /** Sets the callback for UI interactions. */
+ @Override
+ public void setCallback(@Nullable Consumer<CompatUIEvent> callback) {
+ mCallback = callback;
}
/**
* Called when the Task info changed. Creates and updates the compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
*
- * @param taskInfo {@link TaskInfo} task the activity is in.
- * @param taskListener listener to handle the Task Surface placement.
+ * @param compatUIInfo {@link CompatUIInfo} encapsulates information about the task and listener
*/
- public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
- @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+ public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
+ final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
+ final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
@@ -466,7 +460,7 @@
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
return new CompatUIWindowManager(context,
- taskInfo, mSyncQueue, mCompatUICallback, taskListener,
+ taskInfo, mSyncQueue, mCallback, taskListener,
mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
mCompatUIConfiguration, this::onRestartButtonClicked);
}
@@ -478,9 +472,9 @@
taskInfoState.first)) {
// We need to show the dialog
mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
- onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+ onCompatInfoChanged(new CompatUIInfo(taskInfoState.first, taskInfoState.second));
} else {
- mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(taskInfoState.first.taskId));
}
}
@@ -575,13 +569,13 @@
private void onRestartDialogCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
- mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+ mCallback.accept(new SizeCompatRestartButtonClicked(stateInfo.first.taskId));
}
private void onRestartDialogDismissCallback(
Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
- onCompatInfoChanged(stateInfo.first, stateInfo.second);
+ onCompatInfoChanged(new CompatUIInfo(stateInfo.first, stateInfo.second));
}
private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 3ab1fad..1931212 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -40,8 +40,10 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated;
+import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
import java.util.function.Consumer;
@@ -50,10 +52,13 @@
*/
class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
- private final CompatUICallback mCallback;
+ @NonNull
+ private final Consumer<CompatUIEvent> mCallback;
+ @NonNull
private final CompatUIConfiguration mCompatUIConfiguration;
+ @NonNull
private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
// Remember the last reported states in case visibility changes due to keyguard or IME updates.
@@ -65,6 +70,7 @@
int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
@VisibleForTesting
+ @NonNull
CompatUIHintsState mCompatUIHintsState;
@Nullable
@@ -73,11 +79,15 @@
private final float mHideScmTolerance;
- CompatUIWindowManager(Context context, TaskInfo taskInfo,
- SyncTransactionQueue syncQueue, CompatUICallback callback,
- ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
- Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
+ CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+ @NonNull SyncTransactionQueue syncQueue,
+ @NonNull Consumer<CompatUIEvent> callback,
+ @Nullable ShellTaskOrganizer.TaskListener taskListener,
+ @Nullable DisplayLayout displayLayout,
+ @NonNull CompatUIHintsState compatUIHintsState,
+ @NonNull CompatUIConfiguration compatUIConfiguration,
+ @NonNull Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>>
+ onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
@@ -122,7 +132,7 @@
updateVisibilityOfViews();
if (mHasSizeCompat) {
- mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+ mCallback.accept(new SizeCompatRestartButtonAppeared(mTaskId));
}
return mLayout;
@@ -177,7 +187,7 @@
mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED
? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED
: CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, mCameraCompatControlState));
mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
}
@@ -188,7 +198,7 @@
return;
}
mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED;
- mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED);
+ mCallback.accept(new CameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED));
mLayout.setCameraControlVisibility(/* show= */ false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
new file mode 100644
index 0000000..4a0cf98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.compatui.api
+
+/**
+ * Abstraction for all the possible Compat UI Component events.
+ */
+interface CompatUIEvent {
+ /**
+ * Unique event identifier
+ */
+ val eventId: Int
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T : CompatUIEvent> asType(): T? = this as? T
+
+ fun <T : CompatUIEvent> asType(clazz: Class<T>): T? {
+ return if (clazz.isInstance(this)) clazz.cast(this) else null
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
new file mode 100644
index 0000000..817e554
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.compatui.api
+
+import java.util.function.Consumer
+
+/**
+ * Abstraction for the objects responsible to handle all the CompatUI components and the
+ * communication with the server.
+ */
+interface CompatUIHandler {
+ /**
+ * Invoked when a new model is coming from the server.
+ */
+ fun onCompatInfoChanged(compatUIInfo: CompatUIInfo)
+
+ /**
+ * Optional reference to the object responsible to send {@link CompatUIEvent}
+ */
+ fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
new file mode 100644
index 0000000..dbbf049
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.compatui.api
+
+import android.app.TaskInfo
+import com.android.wm.shell.ShellTaskOrganizer
+
+/**
+ * Encapsulate the info of the message from core.
+ */
+data class CompatUIInfo(val taskInfo: TaskInfo, val listener: ShellTaskOrganizer.TaskListener?)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
new file mode 100644
index 0000000..58ce8ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.compatui.impl
+
+import android.app.AppCompatTaskInfo
+import android.app.CameraCompatTaskInfo
+import com.android.wm.shell.compatui.api.CompatUIEvent
+
+internal const val SIZE_COMPAT_RESTART_BUTTON_APPEARED = 0
+internal const val SIZE_COMPAT_RESTART_BUTTON_CLICKED = 1
+internal const val CAMERA_CONTROL_STATE_UPDATE = 2
+
+/**
+ * All the {@link CompatUIEvent} the Compat UI Framework can handle
+ */
+sealed class CompatUIEvents(override val eventId: Int) : CompatUIEvent {
+ /** Sent when the size compat restart button appears. */
+ data class SizeCompatRestartButtonAppeared(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED)
+
+ /** Sent when the size compat restart button is clicked. */
+ data class SizeCompatRestartButtonClicked(val taskId: Int) :
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED)
+
+ /** Sent when the camera compat control state is updated. */
+ data class CameraControlStateUpdated(
+ val taskId: Int,
+ @CameraCompatTaskInfo.CameraCompatControlState val state: Int
+ ) : CompatUIEvents(CAMERA_CONTROL_STATE_UPDATE)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
new file mode 100644
index 0000000..a181eaf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIEvent
+import com.android.wm.shell.compatui.api.CompatUIHandler
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import java.util.function.Consumer
+
+/**
+ * Default implementation of {@link CompatUIHandler} to handle CompatUI components
+ */
+class DefaultCompatUIHandler : CompatUIHandler {
+
+ private var compatUIEventSender: Consumer<CompatUIEvent>? = null
+ override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
+ // Empty at the moment
+ }
+
+ override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
+ this.compatUIEventSender = compatUIEventSender
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 609e5af..9bdc0b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -71,6 +71,8 @@
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -88,12 +90,12 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.TaskStackTransitionObserver;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -211,7 +213,7 @@
Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
- Optional<CompatUIController> compatUI,
+ Optional<CompatUIHandler> compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -230,7 +232,7 @@
@WMSingleton
@Provides
- static Optional<CompatUIController> provideCompatUIController(
+ static Optional<CompatUIHandler> provideCompatUIController(
Context context,
ShellInit shellInit,
ShellController shellController,
@@ -247,6 +249,9 @@
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
+ if (Flags.appCompatUiFramework()) {
+ return Optional.of(new DefaultCompatUIHandler());
+ }
return Optional.of(
new CompatUIController(
context,
@@ -898,7 +903,7 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopTasksController.flatMap((lazy)-> {
+ return desktopTasksController.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
@@ -917,7 +922,7 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- return desktopModeTaskRepository.flatMap((lazy)-> {
+ return desktopModeTaskRepository.flatMap((lazy) -> {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(lazy.get());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e792f7a..aa499d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -77,10 +77,10 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -585,9 +585,10 @@
@Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context
- ) {
- return new ExitDesktopTaskTransitionHandler(transitions, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ExitDesktopTaskTransitionHandler(
+ transitions, context, interactionJankMonitor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index a67dee3..275f725d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -46,7 +46,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e247912..985901d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -74,9 +74,9 @@
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
-import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
-import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index c85f76d..3f5bd1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -25,7 +25,7 @@
import androidx.annotation.VisibleForTesting
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionObserver
import com.android.wm.shell.util.KtProtoLog
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index dae75f9..f01f645 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,7 +23,7 @@
import android.window.TransitionInfo
import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 891f75c..171378f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -42,6 +42,8 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -60,6 +62,7 @@
private final Context mContext;
private final Transitions mTransitions;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -67,17 +70,21 @@
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context) {
- this(transitions, SurfaceControl.Transaction::new, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
- Context context) {
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
+ mInteractionJankMonitor = interactionJankMonitor;
}
/**
@@ -146,6 +153,8 @@
final int screenHeight = metrics.heightPixels;
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
+ mInteractionJankMonitor
+ .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
@@ -175,6 +184,7 @@
if (mOnAnimationFinishedCallback != null) {
mOnAnimationFinishedCallback.accept(finishT);
}
+ mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index b1cbe8d..3572d16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -97,7 +97,7 @@
adb logcat -s "SurfaceControlRegistry"
```
-## Tracing activity starts in the app process
+## Tracing activity starts & finishes in the app process
It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
@@ -113,6 +113,19 @@
adb reboot
```
+Likewise, to trace where a finish() call may be made in the app process, you can enable this system
+property:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.finish_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.finish_activity \"\"
+adb reboot
+```
+
## Dumps
Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index b3c3a3d..e0b0866 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -52,6 +52,7 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -353,6 +354,12 @@
pd.dragSession.initialize();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Hiding task surface: taskId=%d", pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, false /* visible */);
+ }
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners(l -> {
l.onDragStarted();
@@ -382,6 +389,13 @@
if (pd.dragLayout.hasDropped()) {
mLogger.logDrop();
} else {
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Re-showing task surface: taskId=%d",
+ pd.dragSession.hideDragSourceTaskId);
+ mShellTaskOrganizer.setTaskSurfaceVisibility(
+ pd.dragSession.hideDragSourceTaskId, true /* visible */);
+ }
pd.activeDragCount--;
pd.dragLayout.hide(event, () -> {
if (pd.activeDragCount == 0) {
@@ -435,7 +449,16 @@
private boolean handleDrop(DragEvent event, PerDisplay pd) {
final SurfaceControl dragSurface = event.getDragSurface();
pd.activeDragCount--;
- return pd.dragLayout.drop(event, dragSurface, () -> {
+ // Find the token of the task to hide as a part of entering split
+ WindowContainerToken hideTaskToken = null;
+ if (pd.dragSession.hideDragSourceTaskId != -1) {
+ ActivityManager.RunningTaskInfo info = mShellTaskOrganizer.getRunningTaskInfo(
+ pd.dragSession.hideDragSourceTaskId);
+ if (info != null) {
+ hideTaskToken = info.token;
+ }
+ }
+ return pd.dragLayout.drop(event, dragSurface, hideTaskToken, () -> {
if (pd.activeDragCount == 0) {
// Hide the window if another drag hasn't been started while animating the drop
setDropTargetWindowVisibility(pd, View.INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9c7476d..95fe8b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -59,6 +59,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.window.WindowContainerToken;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -234,8 +235,13 @@
return null;
}
+ /**
+ * Handles the drop on a given {@param target}. If a {@param hideTaskToken} is set, then the
+ * handling of the drop will attempt to hide the given task as a part of the same window
+ * container transaction if possible.
+ */
@VisibleForTesting
- void handleDrop(Target target) {
+ void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -254,16 +260,17 @@
? mFullscreenStarter
: mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, starter, position);
+ launchApp(mSession, starter, position, hideTaskToken);
} else {
- launchIntent(mSession, starter, position);
+ launchIntent(mSession, starter, position, hideTaskToken);
}
}
/**
* Launches an app provided by SysUI.
*/
- private void launchApp(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
position);
final ClipDescription description = session.getClipDescription();
@@ -283,8 +290,12 @@
if (isTask) {
final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
- starter.startTask(taskId, position, opts);
+ starter.startTask(taskId, position, opts, hideTaskToken);
} else if (isShortcut) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Can not hide task token with starting shortcut");
+ }
final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
starter.startShortcut(packageName, id, position, opts, user);
@@ -297,14 +308,15 @@
}
}
starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
- position, opts);
+ position, opts, hideTaskToken);
}
}
/**
* Launches an intent sender provided by an application.
*/
- private void launchIntent(DragSession session, Starter starter, @SplitPosition int position) {
+ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -319,18 +331,20 @@
final Bundle opts = baseActivityOpts.toBundle();
starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
- null /* fillIntent */, position, opts);
+ null /* fillIntent */, position, opts, hideTaskToken);
}
/**
* Interface for actually committing the task launches.
*/
public interface Starter {
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options);
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options);
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -352,7 +366,12 @@
}
@Override
- public void startTask(int taskId, int position, @Nullable Bundle options) {
+ public void startTask(int taskId, int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
} catch (RemoteException e) {
@@ -375,7 +394,12 @@
@Override
public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- int position, @Nullable Bundle options) {
+ int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ if (hideTaskToken != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Default starter does not support hide task token");
+ }
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 910175e..f0514e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -51,8 +51,10 @@
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
@@ -483,13 +485,13 @@
/**
* Handles the drop onto a target and animates out the visible drop targets.
*/
- public boolean drop(DragEvent event, SurfaceControl dragSurface,
- Runnable dropCompleteCallback) {
+ public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
+ @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
final boolean handledDrop = mCurrentTarget != null;
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget);
+ mPolicy.handleDrop(mCurrentTarget, hideTaskToken);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -499,7 +501,7 @@
return handledDrop;
}
- private void hideDragSurface(SurfaceControl dragSurface) {
+ private void hideDragSurface(@NonNull SurfaceControl dragSurface) {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
// Currently the splash icon animation runs with the default ValueAnimator duration of
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 3bedef2..dcbdfa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -27,6 +28,7 @@
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.PersistableBundle;
import androidx.annotation.Nullable;
@@ -63,6 +65,7 @@
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
+ int hideDragSourceTaskId = -1;
DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data, int dragFlags) {
@@ -70,6 +73,11 @@
mInitialDragData = data;
mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
+ hideDragSourceTaskId = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
+ : -1;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Extracting drag source taskId: taskId=%d", hideDragSourceTaskId);
}
/**
@@ -84,16 +92,27 @@
* Updates the running task for this drag session.
*/
void updateRunningTask() {
+ final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
final List<ActivityManager.RunningTaskInfo> tasks =
- mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+ mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
+ false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
- final ActivityManager.RunningTaskInfo task = tasks.get(0);
- runningTaskInfo = task;
- runningTaskWinMode = task.getWindowingMode();
- runningTaskActType = task.getActivityType();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Running task: id=%d component=%s", task.taskId,
- task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Skipping running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ continue;
+ }
+ runningTaskInfo = task;
+ runningTaskWinMode = task.getWindowingMode();
+ runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
+ break;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 1641668..4531967 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -29,7 +29,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 814eaae..9539a45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -37,6 +37,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.IRecentsAnimationRunner;
+import android.window.WindowContainerToken;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -52,9 +53,9 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -457,11 +458,31 @@
}
/**
- * Find the background task that match the given component.
+ * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
+ * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask(
+ @Nullable WindowContainerToken ignoreTaskToken) {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2,
+ false /* filterOnlyVisibleRecents */);
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
+ return task;
+ }
+ return null;
+ }
+
+ /**
+ * Find the background task that match the given component. Ignores tasks match
+ * {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
- int userId) {
+ int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
@@ -473,6 +494,9 @@
if (task.isVisible) {
continue;
}
+ if (task.token.equals(ignoreTaskToken)) {
+ continue;
+ }
if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) {
return task;
}
@@ -640,7 +664,7 @@
@Override
public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) {
final ActivityManager.RunningTaskInfo[][] tasks =
- new ActivityManager.RunningTaskInfo[][] {null};
+ new ActivityManager.RunningTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRunningTasks",
(controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum)
.toArray(new ActivityManager.RunningTaskInfo[0]),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index b4941a5..e659151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -64,6 +64,7 @@
import android.view.WindowManager;
import android.widget.Toast;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -526,7 +527,15 @@
mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
}
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Starts an existing task into split.
+ * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead
+ * @param hideTaskToken is not supported.
+ */
+ public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Legacy startTask does not support hide task token");
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
@@ -584,8 +593,8 @@
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
- user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null),
+ user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) {
if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
@@ -676,10 +685,11 @@
* See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
- public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
+ public void startIntentWithInstanceId(PendingIntent intent, int userId,
+ @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
+ @NonNull InstanceId instanceId) {
mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, userId, fillInIntent, position, options);
+ startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
}
private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@@ -825,9 +835,15 @@
instanceId);
}
+ /**
+ * Starts the given intent into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -838,23 +854,24 @@
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
final String packageName1 = SplitScreenUtils.getPackageName(intent);
- final String packageName2 = getPackageName(reverseSplitPosition(position));
- final int userId2 = getUserId(reverseSplitPosition(position));
+ final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
+ final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
final ComponentName component = intent.getIntent().getComponent();
// To prevent accumulating large number of instances in the background, reuse task
// in the background. If we don't explicitly reuse, new may be created even if the app
// isn't multi-instance because WM won't automatically remove/reuse the previous instance
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
+ hideTaskToken))
.orElse(null);
if (taskInfo != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startTask(taskInfo.taskId, position, options);
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
} else {
- startTask(taskInfo.taskId, position, options);
+ startTask(taskInfo.taskId, position, options, hideTaskToken);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
@@ -879,19 +896,23 @@
}
}
- mStageCoordinator.startIntent(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken);
}
- /** Retrieve package name of a specific split position if split screen is activated, otherwise
- * returns the package name of the top running task. */
+ /**
+ * Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task.
+ * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls
+ */
@Nullable
- private String getPackageName(@SplitPosition int position) {
+ private String getPackageName(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return null;
@@ -901,15 +922,19 @@
return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
- /** Retrieve user id of a specific split position if split screen is activated, otherwise
- * returns the user id of the top running task. */
- private int getUserId(@SplitPosition int position) {
+ /**
+ * Retrieve user id of a specific split position if split screen is activated, otherwise
+ * returns the user id of the top running task.
+ * TODO: Merge this with getPackageName() so we don't make multiple binder calls
+ */
+ private int getUserId(@SplitPosition int position,
+ @Nullable WindowContainerToken ignoreTaskToken) {
ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
taskInfo = getTaskInfo(position);
} else {
taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask())
+ .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken))
.orElse(null);
if (!isValidToSplit(taskInfo)) {
return -1;
@@ -1290,7 +1315,8 @@
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> controller.startTask(taskId, position, options));
+ (controller) -> controller.startTask(taskId, position, options,
+ null /* hideTaskToken */));
}
@Override
@@ -1402,8 +1428,8 @@
public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> controller.startIntent(intent, userId, fillInIntent, position,
- options, instanceId));
+ (controller) -> controller.startIntentWithInstanceId(intent, userId,
+ fillInIntent, position, options, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d9e9776..4104234 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -592,12 +592,21 @@
}
}
- /** Use this method to launch an existing Task via a taskId */
- void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ /**
+ * Use this method to launch an existing Task via a taskId.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
+ void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.startTask(taskId, options);
// If this should be mixed, send the task to avoid split handle transition directly.
if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) {
@@ -623,9 +632,13 @@
extraTransitType, !mIsDropEntering);
}
- /** Launches an activity into split. */
+ /**
+ * Launches an activity into split.
+ * @param hideTaskToken If non-null, a task matching this token will be moved to back in the
+ * same window container transaction as the starting of the intent.
+ */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
+ @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
@@ -636,6 +649,10 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ if (hideTaskToken != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
+ wct.reorder(hideTaskToken, false /* onTop */);
+ }
wct.sendPendingIntent(intent, fillInIntent, options);
// If this should be mixed, just send the intent to avoid split handle transition directly.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd4595a..3a68009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -27,7 +27,6 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -76,7 +75,6 @@
*/
@VisibleForTesting
public void init() {
- ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 440fc4d..1be33e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -91,7 +91,7 @@
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 2a95fa3..b62194c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -68,7 +68,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index d48ce53..2fd3eaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -33,7 +33,7 @@
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 216990c..03dbbb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -55,7 +55,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index b8a19ad..a77fd51 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -9,7 +9,7 @@
chenghsiuchang@google.com
atsjenk@google.com
jorgegil@google.com
-nmusgrave@google.com
+vaniadesmonda@google.com
pbdr@google.com
tkachenkoi@google.com
mpodolian@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
new file mode 100644
index 0000000..20e2167c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base scenario test for maximize app window CUJ in desktop mode. */
+@Ignore("Base Test Class")
+abstract class MaximizeAppWindow
+{
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
+ Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWindow() {
+ testApp.maximiseDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 92be4f9..6b69542 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -59,6 +59,7 @@
"guava-android-testlib",
"com.android.window.flags.window-aconfig-java",
"platform-test-annotations",
+ "flag-junit",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 8303317..e91828b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -45,6 +45,7 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -63,6 +64,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -70,6 +73,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -371,7 +375,7 @@
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
clearInvocations(mCompatUI);
@@ -381,7 +385,7 @@
taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Not show size compat UI if task is not visible.
clearInvocations(mCompatUI);
@@ -391,11 +395,11 @@
taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -410,7 +414,7 @@
// Task listener sent to compat UI is null if top activity isn't eligible for letterbox
// education.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null if top activity is eligible for letterbox education and task
// is visible.
@@ -421,7 +425,7 @@
taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Task listener is null if task is invisible.
clearInvocations(mCompatUI);
@@ -431,11 +435,11 @@
taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -451,7 +455,7 @@
// Task listener sent to compat UI is null if top activity doesn't request a camera
// compat control.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null when request a camera compat control for a visible task.
clearInvocations(mCompatUI);
@@ -462,7 +466,7 @@
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// CompatUIController#onCompatInfoChanged is called when requested state for a camera
// compat control changes for a visible task.
@@ -474,7 +478,7 @@
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo3.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, taskListener);
// CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
// mode for a visible task that has a compat control.
@@ -487,7 +491,7 @@
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
taskInfo4.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo4);
- verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo4, taskListener);
// Task linster is null when a camera compat control is dimissed for a visible task.
clearInvocations(mCompatUI);
@@ -498,7 +502,7 @@
CAMERA_COMPAT_CONTROL_DISMISSED;
taskInfo5.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo5);
- verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo5, null /* taskListener */);
// Task linster is null when request a camera compat control for a invisible task.
clearInvocations(mCompatUI);
@@ -509,11 +513,11 @@
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
taskInfo6.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo6);
- verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo6, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -640,7 +644,8 @@
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId);
+ mOrganizer.onSizeCompatRestartButtonClicked(
+ new CompatUIEvents.SizeCompatRestartButtonClicked(task1.taskId));
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
@@ -713,4 +718,13 @@
taskInfo.isVisible = true;
return taskInfo;
}
+
+ private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener listener) {
+ final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class);
+ verify(mCompatUI).onCompatInfoChanged(capture.capture());
+ final CompatUIInfo captureValue = capture.getValue();
+ assertEquals(captureValue.getTaskInfo(), taskInfo);
+ assertEquals(captureValue.getListener(), listener);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 636c632..f5847cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -70,7 +70,7 @@
mContext,
configuration, mCallbacks);
splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
- mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ mDividerView = spy(splitWindowManager.getDividerView());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 9c00864..fc7a777 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -38,6 +38,9 @@
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -45,6 +48,7 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
@@ -55,6 +59,7 @@
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -63,6 +68,7 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,6 +88,10 @@
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
@@ -168,28 +178,32 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
// Verify that the compat controls are added with non-null task listener.
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -202,7 +216,7 @@
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -213,9 +227,9 @@
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- /* taskListener= */ null);
+ /* taskListener= */ null));
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
@@ -223,6 +237,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
@@ -230,7 +245,7 @@
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -240,7 +255,7 @@
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -253,6 +268,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
@@ -260,7 +276,7 @@
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -270,7 +286,7 @@
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -282,7 +298,7 @@
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -296,6 +312,7 @@
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayAdded() {
mController.onDisplayAdded(DISPLAY_ID);
mController.onDisplayAdded(DISPLAY_ID + 1);
@@ -305,11 +322,11 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -328,9 +345,10 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayConfigurationChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
@@ -346,10 +364,11 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
@@ -373,9 +392,10 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnImeShowHide() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
@@ -387,7 +407,7 @@
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -405,9 +425,10 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
// Verify that the restart button is hidden after keyguard becomes showing.
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -419,7 +440,7 @@
// Verify button remains hidden while keyguard is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -437,9 +458,10 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -466,9 +488,10 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -495,6 +518,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutRecreatedIfNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -502,14 +526,15 @@
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(2))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutNotRecreatedIfNotNeeded() {
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -517,14 +542,15 @@
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(1))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() {
// Simulate user aspect ratio button being shown for previous task
mController.setHasShownUserAspectRatioSettingsButton(true);
@@ -545,6 +571,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -606,6 +633,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -634,6 +662,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
// Create new task
final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
@@ -674,7 +703,7 @@
CAMERA_COMPAT_CONTROL_HIDDEN);
taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index cd3e8cb..33d69f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -23,6 +23,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@@ -31,6 +32,9 @@
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,16 +44,20 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -70,8 +78,12 @@
private static final int TASK_ID = 1;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@@ -101,6 +113,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForRestartButton() {
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
@@ -117,6 +130,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForRestartButton() {
doNothing().when(mWindowManager).onRestartButtonLongClicked();
@@ -127,6 +141,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -137,6 +152,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -145,16 +161,17 @@
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -163,16 +180,17 @@
button.performClick();
verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
button.performClick();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
+ CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -181,12 +199,12 @@
button.performClick();
verify(mWindowManager).onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show */ false);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraTreatmentButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -198,6 +216,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForCameraDismissButton() {
doNothing().when(mWindowManager).onCameraButtonLongClicked();
@@ -208,6 +227,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForCameraCompatHint() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
@@ -229,4 +249,15 @@
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 41a81c1..eb3da8f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -42,6 +43,9 @@
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -60,6 +64,8 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import junit.framework.Assert;
@@ -85,12 +91,16 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int TASK_ID = 1;
private static final int TASK_WIDTH = 2000;
private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@@ -129,6 +139,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
@@ -174,6 +185,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateCameraCompatControl() {
// Doesn't create layout if show is false.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -212,6 +224,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -224,6 +237,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -315,6 +329,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.createLayout(/* canShow= */ false);
@@ -347,6 +362,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -366,6 +382,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -390,6 +407,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.mLayout = null;
@@ -415,6 +433,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -423,37 +442,38 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraDismissButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
verify(mLayout).setCameraControlVisibility(/* show= */ false);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraTreatmentButtonClicked() {
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
mWindowManager.createLayout(/* canShow= */ true);
clearInvocations(mLayout);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mWindowManager.onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- verify(mLayout).updateCameraTreatmentButton(
+ verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID,
CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
@@ -468,8 +488,9 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonLongClicked_showHint() {
- // Not create hint popup.
+ // Not create hint popup.
mWindowManager.mHasSizeCompat = true;
mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -483,6 +504,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCameraControlLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
@@ -498,6 +520,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
@@ -506,6 +529,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testShouldShowSizeCompatRestartButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
@@ -558,4 +582,15 @@
taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
return taskInfo;
}
+
+ private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) {
+ final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass(
+ CompatUIEvent.class);
+ verify(mCallback).accept(captureValue.capture());
+ final CompatUIEvents.CameraControlStateUpdated compatUIEvent =
+ (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue();
+ Assert.assertEquals((compatUIEvent).getTaskId(), taskId);
+ Assert.assertEquals((compatUIEvent).getState(), state);
+ clearInvocations(mCallback);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index 172c263..e8191db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,6 +37,7 @@
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +60,10 @@
private View mDismissButton;
private View mDialogContainer;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -66,6 +76,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_education_dialog_container));
@@ -76,6 +87,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -83,6 +95,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClicked() {
assertTrue(mLayout.performClick());
@@ -90,6 +103,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -97,6 +111,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index a60a1cb..b5664ac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +38,9 @@
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -61,6 +65,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -116,6 +121,10 @@
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -153,6 +162,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -162,6 +172,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, /* isDocked */ true);
@@ -172,6 +183,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -182,6 +194,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -192,6 +205,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -238,6 +252,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ false);
@@ -271,6 +286,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -288,6 +304,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -316,6 +333,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -329,6 +347,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_canShowFalse_doesNothing() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -343,6 +362,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -364,6 +384,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease_animationIsCancelled() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -374,6 +395,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testDeviceThemeChange_educationDialogUnseen_recreated() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 4f71b83..0da14d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -23,6 +25,9 @@
import static org.mockito.Mockito.verify;
import android.app.TaskInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -34,6 +39,7 @@
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -62,6 +68,10 @@
@Mock
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -74,6 +84,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertNotNull(mMoveUpButton);
assertNotNull(mMoveDownButton);
@@ -82,6 +93,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
false, /* letterboxVerticalPosition */
@@ -94,6 +106,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
@@ -106,6 +119,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 5867a85..eafb414 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -16,12 +16,17 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -35,6 +40,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -65,6 +71,10 @@
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -80,6 +90,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
@@ -87,6 +98,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -97,6 +109,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
index e2dcdb0..6b0c5dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,9 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +39,7 @@
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@
private View mDialogContainer;
private CheckBox mDontRepeatCheckBox;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_restart_dialog_container));
@@ -86,6 +97,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -93,6 +105,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithoutCheckbox() {
mDontRepeatCheckBox.setChecked(false);
assertTrue(mRestartButton.performClick());
@@ -101,6 +114,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithCheckbox() {
mDontRepeatCheckBox.setChecked(true);
assertTrue(mRestartButton.performClick());
@@ -109,6 +123,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClickedDoesntDismiss() {
assertFalse(mLayout.performClick());
@@ -116,6 +131,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -124,6 +140,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
@@ -135,6 +152,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetRestartOnClickListenerNull() {
mLayout.setRestartOnClickListener(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
index 9f109a1..cfeef90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -33,6 +38,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,6 +66,10 @@
private RestartDialogWindowManager mWindowManager;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -76,6 +86,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -86,6 +97,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 0231612..3fa21ce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -19,6 +19,7 @@
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -28,6 +29,9 @@
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -47,6 +51,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -86,6 +91,10 @@
private UserAspectRatioSettingsLayout mLayout;
private TaskInfo mTaskInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -107,6 +116,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsButton() {
final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
button.performClick();
@@ -123,6 +133,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForUserAspectRatioButton() {
doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
@@ -133,6 +144,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsHint() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 94e168e..9f288cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -22,6 +22,7 @@
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,6 +40,9 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
@@ -61,6 +65,7 @@
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -107,6 +112,10 @@
private TestShellExecutor mExecutor;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -138,6 +147,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateUserAspectRatioButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -178,6 +188,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -190,6 +201,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -242,6 +254,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ false);
@@ -267,6 +280,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -292,6 +306,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -310,6 +325,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -329,6 +345,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -353,6 +370,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.removeLayout();
@@ -378,6 +396,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutHasUserAspectRatioSettingsButton() {
clearInvocations(mWindowManager);
spyOn(mWindowManager);
@@ -411,6 +430,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -419,6 +439,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonClicked() {
mWindowManager.onUserAspectRatioSettingsButtonClicked();
@@ -433,6 +454,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -448,6 +470,7 @@
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 665bed0..d459639 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -52,7 +52,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index bd38d36..6cabbf9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -20,6 +20,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -89,7 +90,7 @@
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -137,6 +138,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
@@ -193,7 +195,6 @@
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(true)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
shellInit = spy(ShellInit(testExecutor))
@@ -263,7 +264,7 @@
}
@Test
- fun instantiate_flagOff_doNotAddInitCallback() {
+ fun instantiate_canNotEnterDesktopMode_doNotAddInitCallback() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 4bfa96a..8d9fd91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -33,7 +33,7 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index b2467e9..e5157c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -45,6 +45,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -65,6 +66,8 @@
@Mock
private Transitions mTransitions;
@Mock
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
IBinder mToken;
@Mock
Supplier<SurfaceControl.Transaction> mTransactionFactory;
@@ -94,7 +97,7 @@
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext);
+ mContext, mInteractionJankMonitor);
mPoint = new Point(0, 0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 582fb91..97fa8d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -289,9 +289,9 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(SPLIT_POSITION_UNDEFINED), any(), any());
}
private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
@@ -304,14 +304,14 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
@@ -324,14 +324,15 @@
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
+ null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3f3cafc..6ec6bed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -36,7 +36,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 5c5a1a2..a0aab2e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,7 +68,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index 3ec65c7..00f37da 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -22,13 +22,10 @@
import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
@@ -39,8 +36,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
/**
* Test class for [DesktopModeFlags]
@@ -188,27 +183,6 @@
}
@Test
- @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun isEnabled_noOverride_featureFlagOnThenOff_returnsTrueAndFalse() {
- setOverride(null)
- // For overridableFlag, in absence of overrides, follow flag
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
-
- val mockitoSession: StaticMockitoSession =
- ExtendedMockito.mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // No caching of flags
- whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
- assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
- } finally {
- mockitoSession.finishMocking()
- }
- }
-
- @Test
@EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() {
@@ -453,15 +427,10 @@
}
private fun resetCache() {
- val cachedToggleOverrideDesktopMode =
- DESKTOP_WINDOWING_MODE::class.java.getDeclaredField("cachedToggleOverride")
- cachedToggleOverrideDesktopMode.isAccessible = true
- cachedToggleOverrideDesktopMode.set(DESKTOP_WINDOWING_MODE, null)
-
- val cachedToggleOverrideWallpaperActivity =
- WALLPAPER_ACTIVITY::class.java.getDeclaredField("cachedToggleOverride")
- cachedToggleOverrideWallpaperActivity.isAccessible = true
- cachedToggleOverrideWallpaperActivity.set(WALLPAPER_ACTIVITY, null)
+ val cachedToggleOverride =
+ DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
+ cachedToggleOverride.isAccessible = true
+ cachedToggleOverride.set(null, null)
// Clear override cache stored in System property
System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 3c387f0..5b95b15 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -49,6 +50,9 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
+import android.os.IBinder;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -195,10 +199,10 @@
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@@ -213,19 +217,20 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() {
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -233,15 +238,16 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
- doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt());
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@@ -249,7 +255,7 @@
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any());
- doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -261,13 +267,13 @@
SPLIT_POSITION_BOTTOM_OR_RIGHT);
// Put the same component into a task in the background
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
- .findTaskInBackground(any(), anyInt());
+ .findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull());
+ isNull(), isNull());
}
@Test
@@ -284,7 +290,7 @@
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
@@ -312,6 +318,7 @@
info.supportsMultiWindow = true;
info.baseIntent = strIntent;
info.baseActivity = strIntent.getComponent();
+ info.token = new WindowContainerToken(mock(IWindowContainerToken.class));
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 0bf5a67..b1803e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -70,7 +70,7 @@
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -360,7 +360,7 @@
isTopActivityStyleFloating = true
numActivities = 1
}
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 8165e59..b355137 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -82,7 +82,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index ac5aeec..e529711 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -34,7 +34,7 @@
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index f3603e1..31c6479 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -75,7 +75,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index 102d21a..43acbb1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -2246,6 +2246,8 @@
clientAttribution.uid = -1; // USE_CALLING_UID
clientAttribution.pid = -1; // USE_CALLING_PID
clientAttribution.deviceId = contextAttribution.deviceId;
+ clientAttribution.packageName = context.getOpPackageName();
+ clientAttribution.attributionTag = context.getAttributionTag();
clientAttribution.next = new AttributionSourceState[0];
return clientAttribution;
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index ad3374a..ac85ab7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -169,10 +169,8 @@
ICameraClient dummyCallbacks = new DummyCameraClient();
- String clientPackageName = getContext().getPackageName();
-
ICamera cameraUser = mUtils.getCameraService()
- .connect(dummyCallbacks, cameraId, clientPackageName,
+ .connect(dummyCallbacks, cameraId,
getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE,
/*forceSlowJpegMode*/false,
@@ -267,8 +265,6 @@
ICameraDeviceCallbacks dummyCallbacks = new DummyCameraDeviceCallbacks();
- String clientPackageName = getContext().getPackageName();
- String clientAttributionTag = getContext().getAttributionTag();
AttributionSourceState clientAttribution =
CameraTestUtils.getClientAttribution(mContext);
clientAttribution.deviceId = DEVICE_ID_DEFAULT;
@@ -277,7 +273,6 @@
ICameraDeviceUser cameraUser =
mUtils.getCameraService().connectDevice(
dummyCallbacks, String.valueOf(cameraId),
- clientPackageName, clientAttributionTag,
0 /*oomScoreOffset*/,
getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 0ab1ee9..35ad924 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -242,9 +242,6 @@
ICameraDeviceCallbacks.Stub dummyCallbacks = new DummyCameraDeviceCallbacks();
- String clientPackageName = getContext().getPackageName();
- String clientAttributionTag = getContext().getAttributionTag();
-
mMockCb = spy(dummyCallbacks);
AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
@@ -252,7 +249,6 @@
clientAttribution.uid = ICameraService.USE_CALLING_UID;
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
- clientPackageName, clientAttributionTag,
/*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6cbf2a..0cb85d8 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -64,6 +64,7 @@
srcs: [
"src/**/*.java",
"src/**/*.kt",
+ "src/**/I*.aidl",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index c9934ad..fb23637 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -115,7 +115,7 @@
content: @Composable (SwitchPreferenceModel) -> Unit,
) {
val context = LocalContext.current
- val restrictedSwitchPreferenceModel = remember(restrictedMode, model.title) {
+ val restrictedSwitchPreferenceModel = remember(restrictedMode, model) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
restrictedSwitchPreferenceModel.RestrictionWrapper {
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 38c871c..403e219 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -102,3 +102,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "extreme_power_low_state_vulnerability"
+ namespace: "pixel_energizer"
+ description: "the battery saver can pause all non-essential apps and their corresponding notification when device is in locked state to introduce the security vulnerability"
+ bug: "346513692"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java
new file mode 100644
index 0000000..1cbb8b4
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A data class representing an action/switch preference. The preference could be one of the four
+ * following forms: 1. Texted row with action to jump to another page 2. Texted row without action
+ * 3. Texted row with action and switch 4. Texted row with switch
+ */
+public class ActionSwitchPreference extends DeviceSettingPreference implements Parcelable {
+ private final String mTitle;
+ private final String mSummary;
+ private final Bitmap mIcon;
+ private final Intent mIntent;
+ private final boolean mHasSwitch;
+ private final boolean mChecked;
+ private final boolean mIsAllowedChangingState;
+ private final Bundle mExtras;
+
+ ActionSwitchPreference(
+ String title,
+ @Nullable String summary,
+ @Nullable Bitmap icon,
+ @Nullable Intent intent,
+ boolean hasSwitch,
+ boolean checked,
+ boolean allowChangingState,
+ @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ validate(title);
+ mTitle = title;
+ mSummary = summary;
+ mIcon = icon;
+ mIntent = intent;
+ mHasSwitch = hasSwitch;
+ mChecked = checked;
+ mIsAllowedChangingState = allowChangingState;
+ mExtras = extras;
+ }
+
+ private static void validate(String title) {
+ if (Objects.isNull(title)) {
+ throw new IllegalArgumentException("Title must be set");
+ }
+ }
+
+ /**
+ * Reads an {@link ActionSwitchPreference} instance from {@link Parcel}
+ * @param in The parcel to read from
+ * @return The instance read
+ */
+ @NonNull
+ public static ActionSwitchPreference readFromParcel(@NonNull Parcel in) {
+ String title = in.readString();
+ String summary = in.readString();
+ Bitmap icon = in.readParcelable(Bitmap.class.getClassLoader());
+ Intent intent = in.readParcelable(Intent.class.getClassLoader());
+ boolean hasSwitch = in.readBoolean();
+ boolean checked = in.readBoolean();
+ boolean allowChangingState = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ActionSwitchPreference(
+ title, summary, icon, intent, hasSwitch, checked, allowChangingState, extras);
+ }
+
+ public static final Creator<ActionSwitchPreference> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ActionSwitchPreference createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ActionSwitchPreference[] newArray(int size) {
+ return new ActionSwitchPreference[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mTitle);
+ dest.writeString(mSummary);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeParcelable(mIntent, flags);
+ dest.writeBoolean(mHasSwitch);
+ dest.writeBoolean(mChecked);
+ dest.writeBoolean(mIsAllowedChangingState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ActionSwitchPreference}. */
+ public static final class Builder {
+ private String mTitle;
+ private String mSummary;
+ private Bitmap mIcon;
+ private Intent mIntent;
+ private boolean mHasSwitch;
+ private boolean mChecked;
+ private boolean mIsAllowedChangingState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the title of the preference.
+ *
+ * @param title The title of the preference.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the summary of the preference, optional.
+ *
+ * @param summary The preference summary.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSummary(@Nullable String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets the icon to be displayed on the left of the preference, optional.
+ *
+ * @param icon The icon.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIcon(@Nullable Bitmap icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the Intent to launch when the preference is clicked, optional.
+ *
+ * @param intent The Intent.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets whether the preference will contain a switch.
+ *
+ * @param hasSwitch Whether the preference contains a switch.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHasSwitch(boolean hasSwitch) {
+ mHasSwitch = hasSwitch;
+ return this;
+ }
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param checked Whether the switch is checked.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setChecked(boolean checked) {
+ mChecked = checked;
+ return this;
+ }
+
+ /**
+ * Sets whether state can be changed by user.
+ *
+ * @param allowChangingState Whether user is allowed to change state.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setAllowedChangingState(boolean allowChangingState) {
+ mIsAllowedChangingState = allowChangingState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ActionSwitchPreference} object.
+ *
+ * @return Returns the built {@link ActionSwitchPreference} object.
+ */
+ @NonNull
+ public ActionSwitchPreference build() {
+ return new ActionSwitchPreference(
+ mTitle,
+ mSummary,
+ mIcon,
+ mIntent,
+ mHasSwitch,
+ mChecked,
+ mIsAllowedChangingState,
+ mExtras);
+ }
+ }
+
+ /**
+ * Gets the title of the preference.
+ *
+ * @return Returns the title of the preference.
+ */
+ @NonNull
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Gets the summary of the preference.
+ *
+ * @return Returns the summary of the preference.
+ */
+ @Nullable
+ public String getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * Gets the icon of the preference.
+ *
+ * @return Returns the icon of the preference.
+ */
+ @Nullable
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Gets the Intent to launch when the preference is clicked.
+ *
+ * @return Returns the intent to launch.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Whether the preference contains a switch.
+ *
+ * @return Whether the preference contains a switch.
+ */
+ public boolean hasSwitch() {
+ return mHasSwitch;
+ }
+
+ /**
+ * Whether the switch is checked.
+ *
+ * @return Whether the switch is checked.
+ */
+ public boolean getChecked() {
+ return mChecked;
+ }
+
+ /**
+ * Gets whether the state can be changed by user.
+ *
+ * @return Whether the state can be changed by user.
+ */
+ public boolean isAllowedChangingState() {
+ return mIsAllowedChangingState;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
new file mode 100644
index 0000000..91c1a59
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing the state of an action/switch preference. */
+public class ActionSwitchPreferenceState extends DeviceSettingPreferenceState
+ implements Parcelable {
+ private final boolean mChecked;
+ private final Bundle mExtras;
+
+ ActionSwitchPreferenceState(boolean checked, @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ mChecked = checked;
+ mExtras = extras;
+ }
+
+ /**
+ * Reads an {@link ActionSwitchPreferenceState} instance from {@link Parcel}
+ * @param in The parcel to read from
+ * @return The instance read
+ */
+ @NonNull
+ public static ActionSwitchPreferenceState readFromParcel(@NonNull Parcel in) {
+ boolean checked = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ActionSwitchPreferenceState(checked, extras);
+ }
+
+ public static final Creator<ActionSwitchPreferenceState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ActionSwitchPreferenceState createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ActionSwitchPreferenceState[] newArray(int size) {
+ return new ActionSwitchPreferenceState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeBoolean(mChecked);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ActionSwitchPreferenceState}. */
+ public static final class Builder {
+ private boolean mChecked;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param checked Whether the switch is checked.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setChecked(boolean checked) {
+ mChecked = checked;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the object.
+ *
+ * @return Returns the built object.
+ */
+ @NonNull
+ public ActionSwitchPreferenceState build() {
+ return new ActionSwitchPreferenceState(mChecked, mExtras);
+ }
+ }
+
+ /**
+ * Whether the switch is checked.
+ *
+ * @return Whether the switch is checked.
+ */
+ public boolean getChecked() {
+ return mChecked;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl
new file mode 100644
index 0000000..acbaf2d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceInfo;
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
new file mode 100644
index 0000000..52e520e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a bluetooth device. */
+public class DeviceInfo implements Parcelable {
+ private final String mBluetoothAddress;
+ private final Bundle mExtras;
+
+ DeviceInfo(String bluetoothAddress, Bundle extras) {
+ validate(bluetoothAddress);
+ mBluetoothAddress = bluetoothAddress;
+ mExtras = extras;
+ }
+
+ private static void validate(String bluetoothAddress) {
+ if (Objects.isNull(bluetoothAddress)) {
+ throw new IllegalArgumentException("Bluetooth address must be set");
+ }
+ }
+
+ /** Read a {@link DeviceInfo} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceInfo readFromParcel(@NonNull Parcel in) {
+ String bluetoothAddress = in.readString();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new DeviceInfo(bluetoothAddress, extras);
+ }
+
+ public static final Creator<DeviceInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceInfo createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceInfo[] newArray(int size) {
+ return new DeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mBluetoothAddress);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link DeviceInfo}. */
+ public static final class Builder {
+ private String mBluetoothAddress;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the bluetooth address of the device, from {@link
+ * android.bluetooth.BluetoothDevice#getAddress()}.
+ *
+ * @param bluetoothAddress The bluetooth address.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setBluetoothAddress(@NonNull String bluetoothAddress) {
+ mBluetoothAddress = bluetoothAddress;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link DeviceInfo} object.
+ *
+ * @return Returns the built {@link DeviceInfo} object.
+ */
+ @NonNull
+ public DeviceInfo build() {
+ return new DeviceInfo(mBluetoothAddress, mExtras);
+ }
+ }
+
+ /**
+ * Gets the bluetooth address of the device.
+ *
+ * @return The bluetooth address from {@link android.bluetooth.BluetoothDevice#getAddress()}.
+ */
+ @NonNull
+ public String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl
new file mode 100644
index 0000000..043cae3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSetting;
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java
new file mode 100644
index 0000000..dc219a9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a device setting item in bluetooth device details page. */
+public final class DeviceSetting implements Parcelable {
+ @DeviceSettingId private final int mSettingId;
+ private final DeviceSettingPreference mPreference;
+ private final Bundle mExtras;
+
+ DeviceSetting(
+ int settingId, @NonNull DeviceSettingPreference preference, @NonNull Bundle extras) {
+ validate(preference);
+ mSettingId = settingId;
+ mPreference = preference;
+ mExtras = extras;
+ }
+
+ private static void validate(DeviceSettingPreference preference) {
+ if (Objects.isNull(preference)) {
+ throw new IllegalArgumentException("Preference must be set");
+ }
+ }
+
+ /** Read a {@link DeviceSetting} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceSetting readFromParcel(@NonNull Parcel in) {
+ int settingId = in.readInt();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ DeviceSettingPreference settingPreference = DeviceSettingPreference.readFromParcel(in);
+ return new DeviceSetting(settingId, settingPreference, extras);
+ }
+
+ public static final Creator<DeviceSetting> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceSetting createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceSetting[] newArray(int size) {
+ return new DeviceSetting[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingId);
+ dest.writeBundle(mExtras);
+ mPreference.writeToParcel(dest, flags);
+ }
+
+ /** Builder class for {@link DeviceSetting}. */
+ public static final class Builder {
+ private int mSettingId;
+ private DeviceSettingPreference mPreference;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSettingId(@DeviceSettingId int settingId) {
+ mSettingId = settingId;
+ return this;
+ }
+
+ /**
+ * Sets the setting preference.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setPreference(@NonNull DeviceSettingPreference settingPreference) {
+ mPreference = settingPreference;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Build the object. */
+ @NonNull
+ public DeviceSetting build() {
+ return new DeviceSetting(mSettingId, mPreference, mExtras);
+ }
+ }
+
+ /**
+ * Gets the setting ID as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the setting ID.
+ */
+ @DeviceSettingId
+ public int getSettingId() {
+ return mSettingId;
+ }
+
+ /**
+ * Gets the setting preference.
+ *
+ * @return Returns the setting preference.
+ */
+ @NonNull
+ public DeviceSettingPreference getPreference() {
+ return mPreference;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
new file mode 100644
index 0000000..20a0339
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+@IntDef(
+ value = {
+ DeviceSettingId.DEVICE_SETTING_ID_UNKNOWN,
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_ADVANCED_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_LE_AUDIO_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_AID_PAIR_OTHER_BUTTON,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_AID_SPACE_LAYOUT,
+ DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_STYLUS,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_EXTRA_CONTROL,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_DEVICE_SLICE_CATEGORY,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_COMPANION_APPS,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_DEVICE_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_EXTRA_OPTIONS,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_RELATED_TOOLS,
+ DeviceSettingId.DEVICE_SETTING_ID_DATA_SYNC_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER,
+ DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ },
+ open = true)
+public @interface DeviceSettingId {
+ /** Device setting ID is unknown. */
+ int DEVICE_SETTING_ID_UNKNOWN = 0;
+
+ /** Device setting ID for header. */
+ int DEVICE_SETTING_ID_HEADER = 1;
+
+ /** Device setting ID for advanced header. */
+ int DEVICE_SETTING_ID_ADVANCED_HEADER = 2;
+
+ /** Device setting ID for LeAudio header. */
+ int DEVICE_SETTING_ID_LE_AUDIO_HEADER = 3;
+
+ /** Device setting ID for hearing aid “pair other” button. */
+ int DEVICE_SETTING_ID_HEARING_AID_PAIR_OTHER_BUTTON = 4;
+
+ /** Device setting ID for hearing aid space layout. */
+ int DEVICE_SETTING_ID_HEARING_AID_SPACE_LAYOUT = 5;
+
+ /** Device setting ID for action buttons(Forget, Connect/Disconnect). */
+ int DEVICE_SETTING_ID_ACTION_BUTTONS = 6;
+
+ /** Device setting ID for stylus device. */
+ int DEVICE_SETTING_ID_DEVICE_STYLUS = 7;
+
+ /** Device setting ID for bluetooth extra control. */
+ int DEVICE_SETTING_ID_BLUETOOTH_EXTRA_CONTROL = 8;
+
+ /** Device setting ID for bluetooth device slice category. */
+ int DEVICE_SETTING_ID_BLUETOOTH_DEVICE_SLICE_CATEGORY = 9;
+
+ /** Device setting ID for device companion apps. */
+ int DEVICE_SETTING_ID_DEVICE_COMPANION_APPS = 10;
+
+ /** Device setting ID for hearing device group. */
+ int DEVICE_SETTING_ID_HEARING_DEVICE_GROUP = 11;
+
+ /** Device setting ID for bluetooth audio device type group. */
+ int DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP = 12;
+
+ /** Device setting ID for spatial audio group. */
+ int DEVICE_SETTING_ID_SPATIAL_AUDIO_GROUP = 13;
+
+ /** Device setting ID for bluetooth profiles. */
+ int DEVICE_SETTING_ID_BLUETOOTH_PROFILES = 14;
+
+ /** Device setting ID for bluetooth extra options. */
+ int DEVICE_SETTING_ID_BLUETOOTH_EXTRA_OPTIONS = 15;
+
+ /** Device setting ID for bluetooth related tools. */
+ int DEVICE_SETTING_ID_BLUETOOTH_RELATED_TOOLS = 16;
+
+ /** Device setting ID for data sync group. */
+ int DEVICE_SETTING_ID_DATA_SYNC_GROUP = 17;
+
+ /** Device setting ID for keyboard settings. */
+ int DEVICE_SETTING_ID_KEYBOARD_SETTINGS = 18;
+
+ /** Device setting ID for device details footer. */
+ int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
+
+ /** Device setting ID for ANC. */
+ int DEVICE_SETTING_ID_ANC = 1001;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
new file mode 100644
index 0000000..9ee33b0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a device settings item in bluetooth device details config.
+ *
+ * @property settingId The setting ID of the item, as defined by IntDef [DeviceSettingId].
+ * @property packageName The package name for service binding.
+ * @property className The class name for service binding.
+ * @property intentAction The intent action for service binding.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingItem(
+ @DeviceSettingId val settingId: Int,
+ val packageName: String,
+ val className: String,
+ val intentAction: String,
+ val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.run {
+ writeInt(settingId)
+ writeString(packageName)
+ writeString(className)
+ writeString(intentAction)
+ writeBundle(extras)
+ }
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<DeviceSettingItem> =
+ object : Parcelable.Creator<DeviceSettingItem> {
+ override fun createFromParcel(parcel: Parcel) =
+ parcel.run {
+ DeviceSettingItem(
+ settingId = readInt(),
+ packageName = readString() ?: "",
+ className = readString() ?: "",
+ intentAction = readString() ?: "",
+ extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
+ )
+ }
+
+ override fun newArray(size: Int): Array<DeviceSettingItem?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
new file mode 100644
index 0000000..790939a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+/** An abstract class representing a device setting preference. */
+public abstract class DeviceSettingPreference {
+ @DeviceSettingType private final int mSettingType;
+
+ public static final DeviceSettingPreference UNKNOWN =
+ new DeviceSettingPreference(DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN) {};
+
+ protected DeviceSettingPreference(@DeviceSettingType int settingType) {
+ mSettingType = settingType;
+ }
+
+ /** Read a {@link DeviceSettingPreference} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceSettingPreference readFromParcel(@NonNull Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH:
+ return ActionSwitchPreference.readFromParcel(in);
+ case DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE:
+ return MultiTogglePreference.readFromParcel(in);
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ /** Writes the instance to {@link Parcel}. */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingType);
+ }
+
+ /**
+ * Gets the setting type, as defined by IntDef {@link DeviceSettingType}.
+ *
+ * @return the setting type.
+ */
+ @DeviceSettingType
+ public int getSettingType() {
+ return mSettingType;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java
new file mode 100644
index 0000000..a982af7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+/** An abstract class representing a device setting preference state. */
+public abstract class DeviceSettingPreferenceState {
+ @DeviceSettingType private final int mSettingType;
+
+ public static final DeviceSettingPreferenceState UNKNOWN =
+ new DeviceSettingPreferenceState(DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN) {};
+
+ protected DeviceSettingPreferenceState(@DeviceSettingType int settingType) {
+ mSettingType = settingType;
+ }
+
+ /** Reads a {@link DeviceSettingPreferenceState} from {@link Parcel}. */
+ @NonNull
+ public static DeviceSettingPreferenceState readFromParcel(@NonNull Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH:
+ return ActionSwitchPreferenceState.readFromParcel(in);
+ case DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE:
+ return MultiTogglePreferenceState.readFromParcel(in);
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ /** Writes the object to parcel. */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingType);
+ }
+
+ /**
+ * Gets the setting type, as defined by IntDef {@link DeviceSettingType}.
+ *
+ * @return The setting type.
+ */
+ @DeviceSettingType
+ public int getSettingType() {
+ return mSettingType;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl
new file mode 100644
index 0000000..61429a6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSettingState;
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
new file mode 100644
index 0000000..63fd4eb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a device setting state. */
+public class DeviceSettingState implements Parcelable {
+ @DeviceSettingId private final int mSettingId;
+ private final DeviceSettingPreferenceState mPreferenceState;
+ private final Bundle mExtras;
+
+ DeviceSettingState(
+ @DeviceSettingId int settingId,
+ @NonNull DeviceSettingPreferenceState preferenceState,
+ @NonNull Bundle extras) {
+ validate(preferenceState);
+ mSettingId = settingId;
+ mPreferenceState = preferenceState;
+ mExtras = extras;
+ }
+
+ private static void validate(DeviceSettingPreferenceState preferenceState) {
+ if (Objects.isNull(preferenceState)) {
+ throw new IllegalArgumentException("PreferenceState must be set");
+ }
+ }
+
+ /** Reads a {@link DeviceSettingState} from {@link Parcel}. */
+ @NonNull
+ public static DeviceSettingState readFromParcel(@NonNull Parcel in) {
+ int settingId = in.readInt();
+ Bundle extra = in.readBundle(Bundle.class.getClassLoader());
+ DeviceSettingPreferenceState preferenceState =
+ DeviceSettingPreferenceState.readFromParcel(in);
+ return new DeviceSettingState(settingId, preferenceState, extra);
+ }
+
+ public static final Creator<DeviceSettingState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceSettingState createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceSettingState[] newArray(int size) {
+ return new DeviceSettingState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Writes the instance to {@link Parcel}. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingId);
+ dest.writeBundle(mExtras);
+ mPreferenceState.writeToParcel(dest, flags);
+ }
+
+ /** Builder class for {@link DeviceSettingState}. */
+ public static final class Builder {
+ private int mSettingId;
+ private DeviceSettingPreferenceState mSettingPreferenceState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSettingId(@DeviceSettingId int settingId) {
+ mSettingId = settingId;
+ return this;
+ }
+
+ /**
+ * Sets the setting preference state.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setPreferenceState(
+ @NonNull DeviceSettingPreferenceState settingPreferenceState) {
+ mSettingPreferenceState = settingPreferenceState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Build the object. */
+ @NonNull
+ public DeviceSettingState build() {
+ return new DeviceSettingState(mSettingId, mSettingPreferenceState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return the setting ID.
+ */
+ @DeviceSettingId
+ public int getSettingId() {
+ return mSettingId;
+ }
+
+ /**
+ * Gets the preference state of the setting.
+ *
+ * @return the setting preference state.
+ */
+ @NonNull
+ public DeviceSettingPreferenceState getPreferenceState() {
+ return mPreferenceState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
new file mode 100644
index 0000000..ee4d90f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+@IntDef(
+ value = {
+ DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN,
+ DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH,
+ DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE,
+ },
+ open = true)
+public @interface DeviceSettingType {
+ /** Device setting type is unknown. */
+ int DEVICE_SETTING_TYPE_UNKNOWN = 0;
+
+ /** Device setting type is action/switch preference. */
+ int DEVICE_SETTING_TYPE_ACTION_SWITCH = 1;
+
+ /** Device setting type is multi-toggle preference. */
+ int DEVICE_SETTING_TYPE_MULTI_TOGGLE = 2;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl
new file mode 100644
index 0000000..3201d13
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSettingsConfig;
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
new file mode 100644
index 0000000..c8a2e9c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a bluetooth device details config.
+ *
+ * @property mainContentItems The setting items to be shown in main page.
+ * @property moreSettingsItems The setting items to be shown in more settings page.
+ * @property moreSettingsFooter The footer in more settings page.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingsConfig(
+ val mainContentItems: List<DeviceSettingItem>,
+ val moreSettingsItems: List<DeviceSettingItem>,
+ val moreSettingsFooter: String,
+ val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.run {
+ writeTypedList(mainContentItems)
+ writeTypedList(moreSettingsItems)
+ writeString(moreSettingsFooter)
+ writeBundle(extras)
+ }
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<DeviceSettingsConfig> =
+ object : Parcelable.Creator<DeviceSettingsConfig> {
+ override fun createFromParcel(parcel: Parcel): DeviceSettingsConfig =
+ parcel.run {
+ DeviceSettingsConfig(
+ mainContentItems =
+ arrayListOf<DeviceSettingItem>().also {
+ readTypedList(it, DeviceSettingItem.CREATOR)
+ },
+ moreSettingsItems =
+ arrayListOf<DeviceSettingItem>().also {
+ readTypedList(it, DeviceSettingItem.CREATOR)
+ },
+ moreSettingsFooter = readString()!!,
+ extras = readBundle((Bundle::class.java.classLoader))!!,
+ )
+ }
+
+ override fun newArray(size: Int): Array<DeviceSettingsConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl
new file mode 100644
index 0000000..385a780
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting;
+
+interface IDeviceSettingsListener {
+ oneway void onDeviceSettingsChanged(in List<DeviceSetting> settings) = 0;
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
new file mode 100644
index 0000000..d5efac9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState;
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener;
+
+oneway interface IDeviceSettingsProviderService {
+ void registerDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+ void unregisterDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+ void updateDeviceSettings(in DeviceInfo device, in DeviceSettingState params);
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
new file mode 100644
index 0000000..01bb6f0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** A data class representing a multi-toggle preference. */
+public class MultiTogglePreference extends DeviceSettingPreference implements Parcelable {
+ private final String mTitle;
+ private final ImmutableList<ToggleInfo> mToggleInfos;
+ private final int mState;
+ private final boolean mIsAllowedChangingState;
+ private final Bundle mExtras;
+
+ MultiTogglePreference(
+ @NonNull String title,
+ List<ToggleInfo> toggleInfos,
+ int state,
+ boolean allowChangingState,
+ Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ validate(title, state);
+ mTitle = title;
+ mToggleInfos = ImmutableList.copyOf(toggleInfos);
+ mState = state;
+ mIsAllowedChangingState = allowChangingState;
+ mExtras = extras;
+ }
+
+ private static void validate(String title, int state) {
+ if (Objects.isNull(title)) {
+ throw new IllegalArgumentException("Title must be set");
+ }
+ if (state < 0) {
+ throw new IllegalArgumentException("State must be a non-negative integer");
+ }
+ }
+
+ /** Read a {@link MultiTogglePreference} from {@link Parcel}. */
+ @NonNull
+ public static MultiTogglePreference readFromParcel(@NonNull Parcel in) {
+ String title = in.readString();
+ List<ToggleInfo> toggleInfos = new ArrayList<>();
+ in.readTypedList(toggleInfos, ToggleInfo.CREATOR);
+ int state = in.readInt();
+ boolean allowChangingState = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new MultiTogglePreference(title, toggleInfos, state, allowChangingState, extras);
+ }
+
+ public static final Creator<MultiTogglePreference> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public MultiTogglePreference createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public MultiTogglePreference[] newArray(int size) {
+ return new MultiTogglePreference[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mTitle);
+ dest.writeTypedList(mToggleInfos, flags);
+ dest.writeInt(mState);
+ dest.writeBoolean(mIsAllowedChangingState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link MultiTogglePreference}. */
+ public static final class Builder {
+ private String mTitle;
+ private ImmutableList.Builder<ToggleInfo> mToggleInfos = new ImmutableList.Builder<>();
+ private int mState;
+ private boolean mAllowChangingState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the title of the preference.
+ *
+ * @param title The title of the preference.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Adds a toggle in the preference.
+ *
+ * @param toggleInfo The toggle to add.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder addToggleInfo(@NonNull ToggleInfo toggleInfo) {
+ mToggleInfos.add(toggleInfo);
+ return this;
+ }
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param state The index of the enabled toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ /**
+ * Sets whether state can be changed by user.
+ *
+ * @param allowChangingState Whether user is allowed to change state.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setAllowChangingState(boolean allowChangingState) {
+ mAllowChangingState = allowChangingState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ToggleInfo} object.
+ *
+ * @return Returns the built {@link ToggleInfo} object.
+ */
+ @NonNull
+ public MultiTogglePreference build() {
+ return new MultiTogglePreference(
+ mTitle, mToggleInfos.build(), mState, mAllowChangingState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the title of the preference.
+ *
+ * @return The title.
+ */
+ @NonNull
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Gets the state of the {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of the enabled toggle.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Gets the toggle list in the preference.
+ *
+ * @return the toggle list.
+ */
+ @NonNull
+ public List<ToggleInfo> getToggleInfos() {
+ return mToggleInfos;
+ }
+
+ /**
+ * Gets whether the state can be changed by user.
+ *
+ * @return Whether the state can be changed by user.
+ */
+ public boolean isAllowedChangingState() {
+ return mIsAllowedChangingState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
new file mode 100644
index 0000000..239df0b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing a multi-toggle preference state. */
+public class MultiTogglePreferenceState extends DeviceSettingPreferenceState implements Parcelable {
+ private final int mState;
+ private final Bundle mExtras;
+
+ MultiTogglePreferenceState(int state, @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ mState = state;
+ mExtras = extras;
+ }
+
+ /** Reads a {@link MultiTogglePreferenceState} from {@link Parcel}. */
+ @NonNull
+ public static MultiTogglePreferenceState readFromParcel(@NonNull Parcel in) {
+ int state = in.readInt();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new MultiTogglePreferenceState(state, extras);
+ }
+
+ public static final Creator<MultiTogglePreferenceState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public MultiTogglePreferenceState createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public MultiTogglePreferenceState[] newArray(int size) {
+ return new MultiTogglePreferenceState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link MultiTogglePreferenceState}. */
+ public static final class Builder {
+ private int mState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the state of {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of enabled toggle.
+ */
+ @NonNull
+ public Builder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Builds the object. */
+ @NonNull
+ public MultiTogglePreferenceState build() {
+ return new MultiTogglePreferenceState(mState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the state of the {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of the enabled toggle.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java
new file mode 100644
index 0000000..7dcf3aa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a toggle in {@link MultiTogglePreference}. */
+public class ToggleInfo implements Parcelable {
+ private final String mLabel;
+ private final Bitmap mIcon;
+ private final Bundle mExtras;
+
+ ToggleInfo(@NonNull String label, @NonNull Bitmap icon, @NonNull Bundle extras) {
+ validate(label, icon);
+ mLabel = label;
+ mIcon = icon;
+ mExtras = extras;
+ }
+
+ private static void validate(String label, Bitmap icon) {
+ if (Objects.isNull(label)) {
+ throw new IllegalArgumentException("Label must be set");
+ }
+ if (Objects.isNull(icon)) {
+ throw new IllegalArgumentException("Icon must be set");
+ }
+ }
+
+ /** Read a {@link ToggleInfo} instance from {@link Parcel}. */
+ @NonNull
+ public static ToggleInfo readFromParcel(@NonNull Parcel in) {
+ String label = in.readString();
+ Bitmap icon = in.readParcelable(Bitmap.class.getClassLoader());
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ToggleInfo(label, icon, extras);
+ }
+
+ public static final Creator<ToggleInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ToggleInfo createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ToggleInfo[] newArray(int size) {
+ return new ToggleInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mLabel);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ToggleInfo}. */
+ public static final class Builder {
+ private Bitmap mIcon;
+ private String mLabel;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the label of the toggle.
+ *
+ * @param label The label of the toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setLabel(@NonNull String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets the icon of the toggle.
+ *
+ * @param icon The icon of the toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIcon(@NonNull Bitmap icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ToggleInfo} object.
+ *
+ * @return Returns the built {@link ToggleInfo} object.
+ */
+ @NonNull
+ public ToggleInfo build() {
+ return new ToggleInfo(mLabel, mIcon, mExtras);
+ }
+ }
+
+ /**
+ * Gets the label of the toggle.
+ *
+ * @return the label to be shown under the toggle
+ */
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Gets the icon of the toggle.
+ *
+ * @return the icon in toggle
+ */
+ @NonNull
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 8bdbee3..9be3159 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -16,11 +16,15 @@
package com.android.settingslib.fuelgauge;
+import static android.provider.Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED;
+import static android.provider.Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED;
+
import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_STATE_MANUAL_UPDATE;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +37,10 @@
import android.util.Log;
import android.util.Slog;
+import androidx.core.util.Function;
+
+import com.android.settingslib.flags.Flags;
+
/**
* Utilities related to battery saver.
*/
@@ -125,6 +133,19 @@
Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
}
final ContentResolver cr = context.getContentResolver();
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+
+ if (Flags.extremePowerLowStateVulnerability()) {
+ var keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (enable
+ && needFirstTimeWarning
+ && keyguardManager != null
+ && keyguardManager.isDeviceLocked()) {
+ var result = powerManager.setPowerSaveModeEnabled(true);
+ Log.d(TAG, "Device is locked, setPowerSaveModeEnabled by default. " + result);
+ return result;
+ }
+ }
final Bundle confirmationExtras = new Bundle(1);
confirmationExtras.putBoolean(EXTRA_CONFIRM_TEXT_ONLY, false);
@@ -136,7 +157,7 @@
setBatterySaverConfirmationAcknowledged(context);
}
- if (context.getSystemService(PowerManager.class).setPowerSaveModeEnabled(enable)) {
+ if (powerManager.setPowerSaveModeEnabled(enable)) {
if (enable) {
final int count =
Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1;
@@ -173,10 +194,7 @@
* @see #EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL
*/
public static boolean maybeShowBatterySaverConfirmation(Context context, Bundle extras) {
- if (Secure.getInt(context.getContentResolver(),
- Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0
- && Secure.getInt(context.getContentResolver(),
- Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) {
+ if (isBatterySaverConfirmationHasBeenShowedBefore(context)) {
// Already shown.
return false;
}
@@ -184,6 +202,17 @@
return true;
}
+ /**
+ * Returns {@code true} if the battery saver confirmation warning has been acknowledged by the
+ * user in the past before.
+ */
+ public static boolean isBatterySaverConfirmationHasBeenShowedBefore(Context context) {
+ Function<String, Integer> secureGetInt =
+ key -> Secure.getInt(context.getContentResolver(), key, /* def= */ 0);
+ return secureGetInt.apply(LOW_POWER_WARNING_ACKNOWLEDGED) != 0
+ && secureGetInt.apply(EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED) != 0;
+ }
+
private static void recordBatterySaverEnabledReason(Context context, boolean enable,
@SaverManualEnabledReason int reason) {
final Bundle enabledReasonExtras = new Bundle(2);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt
new file mode 100644
index 0000000..02d684d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.settingslib.media.data.repository
+
+import android.media.AudioManager
+import android.media.IVolumeController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** Returns [AudioManager.setVolumeController] events as a [Flow] */
+fun AudioManager.volumeControllerEvents(): Flow<VolumeControllerEvent> =
+ callbackFlow {
+ volumeController =
+ object : IVolumeController.Stub() {
+ override fun displaySafeVolumeWarning(flags: Int) {
+ launch { send(VolumeControllerEvent.DisplaySafeVolumeWarning(flags)) }
+ }
+
+ override fun volumeChanged(streamType: Int, flags: Int) {
+ launch { send(VolumeControllerEvent.VolumeChanged(streamType, flags)) }
+ }
+
+ override fun masterMuteChanged(flags: Int) {
+ launch { send(VolumeControllerEvent.MasterMuteChanged(flags)) }
+ }
+
+ override fun setLayoutDirection(layoutDirection: Int) {
+ launch { send(VolumeControllerEvent.SetLayoutDirection(layoutDirection)) }
+ }
+
+ override fun dismiss() {
+ launch { send(VolumeControllerEvent.Dismiss) }
+ }
+
+ override fun setA11yMode(mode: Int) {
+ launch { send(VolumeControllerEvent.SetA11yMode(mode)) }
+ }
+
+ override fun displayCsdWarning(
+ csdWarning: Int,
+ displayDurationMs: Int,
+ ) {
+ launch {
+ send(
+ VolumeControllerEvent.DisplayCsdWarning(
+ csdWarning,
+ displayDurationMs,
+ )
+ )
+ }
+ }
+ }
+ awaitClose { volumeController = null }
+ }
+ .buffer()
+
+/** Models events received via [IVolumeController] */
+sealed interface VolumeControllerEvent {
+
+ /** @see [IVolumeController.displaySafeVolumeWarning] */
+ data class DisplaySafeVolumeWarning(val flags: Int) : VolumeControllerEvent
+
+ /** @see [IVolumeController.volumeChanged] */
+ data class VolumeChanged(val streamType: Int, val flags: Int) : VolumeControllerEvent
+
+ /** @see [IVolumeController.masterMuteChanged] */
+ data class MasterMuteChanged(val flags: Int) : VolumeControllerEvent
+
+ /** @see [IVolumeController.setLayoutDirection] */
+ data class SetLayoutDirection(val layoutDirection: Int) : VolumeControllerEvent
+
+ /** @see [IVolumeController.setA11yMode] */
+ data class SetA11yMode(val mode: Int) : VolumeControllerEvent
+
+ /** @see [IVolumeController.displayCsdWarning] */
+ data class DisplayCsdWarning(
+ val csdWarning: Int,
+ val displayDurationMs: Int,
+ ) : VolumeControllerEvent
+
+ /** @see [IVolumeController.dismiss] */
+ data object Dismiss : VolumeControllerEvent
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt
new file mode 100644
index 0000000..83b612d
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.settingslib.media.data.repository
+
+import android.media.AudioManager
+import android.media.IVolumeController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AudioManagerVolumeControllerExtTest {
+
+ private val testScope = TestScope()
+
+ @Captor private lateinit var volumeControllerCaptor: ArgumentCaptor<IVolumeController>
+ @Mock private lateinit var audioManager: AudioManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun displaySafeVolumeWarning_emitsEvent() =
+ testEvent(VolumeControllerEvent.DisplaySafeVolumeWarning(1)) { displaySafeVolumeWarning(1) }
+
+ @Test
+ fun volumeChanged_emitsEvent() =
+ testEvent(VolumeControllerEvent.VolumeChanged(1, 2)) { volumeChanged(1, 2) }
+
+ @Test
+ fun masterMuteChanged_emitsEvent() =
+ testEvent(VolumeControllerEvent.MasterMuteChanged(1)) { masterMuteChanged(1) }
+
+ @Test
+ fun setLayoutDirection_emitsEvent() =
+ testEvent(VolumeControllerEvent.SetLayoutDirection(1)) { setLayoutDirection(1) }
+
+ @Test
+ fun setA11yMode_emitsEvent() =
+ testEvent(VolumeControllerEvent.SetA11yMode(1)) { setA11yMode(1) }
+
+ @Test
+ fun displayCsdWarning_emitsEvent() =
+ testEvent(VolumeControllerEvent.DisplayCsdWarning(1, 2)) { displayCsdWarning(1, 2) }
+
+ @Test fun dismiss_emitsEvent() = testEvent(VolumeControllerEvent.Dismiss) { dismiss() }
+
+ private fun testEvent(
+ expectedEvent: VolumeControllerEvent,
+ emit: IVolumeController.() -> Unit,
+ ) =
+ testScope.runTest {
+ var event: VolumeControllerEvent? = null
+ audioManager.volumeControllerEvents().onEach { event = it }.launchIn(backgroundScope)
+ runCurrent()
+ verify(audioManager).volumeController = volumeControllerCaptor.capture()
+
+ volumeControllerCaptor.value.emit()
+ runCurrent()
+
+ assertThat(event).isEqualTo(expectedEvent)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java
new file mode 100644
index 0000000..e8e1556
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ActionSwitchPreferenceStateTest {
+
+ @Test
+ public void getMethods() {
+ ActionSwitchPreferenceState state1 =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ ActionSwitchPreferenceState state2 =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(false)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ assertThat(state1.getChecked()).isTrue();
+ assertThat(state2.getChecked()).isFalse();
+ assertThat(state1.getExtras().getString("key1")).isEqualTo("value1");
+ assertThat(state2.getExtras().getString("key2")).isEqualTo("value2");
+ }
+
+ @Test
+ public void parcelOperation_notChecked() {
+ ActionSwitchPreferenceState state =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(false)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ ActionSwitchPreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getChecked()).isEqualTo(state.getChecked());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_checked() {
+ ActionSwitchPreferenceState state =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(true)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ ActionSwitchPreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getChecked()).isEqualTo(state.getChecked());
+ assertThat(fromParcel.getExtras().getString("key2"))
+ .isEqualTo(state.getExtras().getString("key2"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ActionSwitchPreferenceState writeAndRead(ActionSwitchPreferenceState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ActionSwitchPreferenceState fromParcel =
+ ActionSwitchPreferenceState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java
new file mode 100644
index 0000000..354d0f6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ActionSwitchPreferenceTest {
+
+ @Test
+ public void build_withoutTitle_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder().setSummary("summary").build();
+ });
+ }
+
+ @Test
+ public void build_withTitle_successfully() {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder().setTitle("title").build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder()
+ .setTitle("title")
+ .setSummary("summary")
+ .setIntent(new Intent("intent_action"))
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setHasSwitch(true)
+ .setChecked(true)
+ .setAllowedChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ Intent intent = new Intent("intent_action");
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(intent).build();
+
+ assertThat(preference.getTitle()).isEqualTo("title");
+ assertThat(preference.getSummary()).isEqualTo("summary");
+ assertThat(preference.getIcon()).isSameInstanceAs(icon);
+ assertThat(preference.getIntent()).isSameInstanceAs(intent);
+ assertThat(preference.hasSwitch()).isTrue();
+ assertThat(preference.getChecked()).isTrue();
+ assertThat(preference.isAllowedChangingState()).isTrue();
+ assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ Intent intent = new Intent("intent_action");
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(intent).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon().sameAs(preference.getIcon())).isTrue();
+ assertThat(fromParcel.getIntent().getAction()).isSameInstanceAs("intent_action");
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_noIntent() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(null).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon().sameAs(preference.getIcon())).isTrue();
+ assertThat(preference.getIntent()).isNull();
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_noIcon() {
+ Intent intent = new Intent("intent_action");
+ ActionSwitchPreference preference = builder().setIcon(null).setIntent(intent).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon()).isNull();
+ assertThat(fromParcel.getIntent().getAction()).isSameInstanceAs("intent_action");
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ActionSwitchPreference writeAndRead(ActionSwitchPreference preference) {
+ Parcel parcel = Parcel.obtain();
+ preference.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ActionSwitchPreference fromParcel = ActionSwitchPreference.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+
+ private ActionSwitchPreference.Builder builder() {
+ return new ActionSwitchPreference.Builder()
+ .setTitle("title")
+ .setSummary("summary")
+ .setHasSwitch(true)
+ .setChecked(true)
+ .setAllowedChangingState(true)
+ .setExtras(buildBundle("key1", "value1"));
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java
new file mode 100644
index 0000000..fd5b075
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceInfoTest {
+ @Test
+ public void build_withoutBluetoothAddress_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceInfo unused =
+ new DeviceInfo.Builder()
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceInfo unused = new DeviceInfo.Builder().setBluetoothAddress("12:34:56:78").build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceInfo unused =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ DeviceInfo info =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(info.getBluetoothAddress()).isEqualTo("12:34:56:78");
+ assertThat(info.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ DeviceInfo info =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceInfo fromParcel = writeAndRead(info);
+
+ assertThat(fromParcel.getBluetoothAddress()).isEqualTo(info.getBluetoothAddress());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(info.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceInfo writeAndRead(DeviceInfo state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
new file mode 100644
index 0000000..56e9b6c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingItemTest {
+
+ @Test
+ fun parcelOperation() {
+ val item =
+ DeviceSettingItem(
+ settingId = 1,
+ packageName = "package_name",
+ className = "class_name",
+ intentAction = "intent_action",
+ extras = Bundle().apply { putString("key1", "value1") },
+ )
+
+ val fromParcel = writeAndRead(item)
+
+ assertThat(fromParcel.settingId).isEqualTo(item.settingId)
+ assertThat(fromParcel.packageName).isEqualTo(item.packageName)
+ assertThat(fromParcel.className).isEqualTo(item.className)
+ assertThat(fromParcel.intentAction).isEqualTo(item.intentAction)
+ assertThat(fromParcel.extras.getString("key1")).isEqualTo(item.extras.getString("key1"))
+ }
+
+ private fun writeAndRead(item: DeviceSettingItem): DeviceSettingItem {
+ val parcel = Parcel.obtain()
+ item.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return DeviceSettingItem.CREATOR.createFromParcel(parcel)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java
new file mode 100644
index 0000000..12b7a0f
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingStateTest {
+ private static final ActionSwitchPreferenceState ACTION_SWITCH_PREFERENCE_STATE =
+ new ActionSwitchPreferenceState.Builder().setChecked(true).build();
+ private static final MultiTogglePreferenceState MULTI_TOGGLE_PREFERENCE_STATE =
+ new MultiTogglePreferenceState.Builder().setState(123).build();
+
+ @Test
+ public void build_withoutPreferenceState_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods_actionSwitchPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(state.getSettingId()).isEqualTo(123);
+ assertThat(state.getPreferenceState()).isInstanceOf(ActionSwitchPreferenceState.class);
+ assertThat(state.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void getMethods_multiTogglePreference() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(MULTI_TOGGLE_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(state.getSettingId()).isEqualTo(123);
+ assertThat(state.getPreferenceState()).isInstanceOf(MultiTogglePreferenceState.class);
+ assertThat(state.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation_actionSwitchPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState()).isInstanceOf(ActionSwitchPreferenceState.class);
+ assertThat(fromParcel.getPreferenceState().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ assertThat(((ActionSwitchPreferenceState) fromParcel.getPreferenceState()).getChecked())
+ .isTrue();
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_multiTogglePreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(MULTI_TOGGLE_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState()).isInstanceOf(MultiTogglePreferenceState.class);
+ assertThat(fromParcel.getPreferenceState().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ assertThat(((MultiTogglePreferenceState) fromParcel.getPreferenceState()).getState())
+ .isEqualTo(123);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_unknownPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(new DeviceSettingPreferenceState(123) {})
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState())
+ .isSameInstanceAs(DeviceSettingPreferenceState.UNKNOWN);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceSettingState writeAndRead(DeviceSettingState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceSettingState fromParcel = DeviceSettingState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java
new file mode 100644
index 0000000..98dc54b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingTest {
+ private static final ActionSwitchPreference ACTION_SWITCH_PREFERENCE =
+ new ActionSwitchPreference.Builder().setTitle("action_switch_preference").build();
+ private static final MultiTogglePreference MULTI_TOGGLE_PREFERENCE =
+ new MultiTogglePreference.Builder().setTitle("multi_toggle_preference").build();
+
+ @Test
+ public void build_withoutPreference_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods_actionSwitchPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(setting.getSettingId()).isEqualTo(123);
+ assertThat(setting.getPreference()).isInstanceOf(ActionSwitchPreference.class);
+ assertThat(setting.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void getMethods_multiTogglePreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(MULTI_TOGGLE_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(setting.getSettingId()).isEqualTo(123);
+ assertThat(setting.getPreference()).isInstanceOf(MultiTogglePreference.class);
+ assertThat(setting.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation_actionSwitchPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isInstanceOf(ActionSwitchPreference.class);
+ assertThat(fromParcel.getPreference().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ assertThat(((ActionSwitchPreference) fromParcel.getPreference()).getTitle())
+ .isEqualTo("action_switch_preference");
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_multiTogglePreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(MULTI_TOGGLE_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isInstanceOf(MultiTogglePreference.class);
+ assertThat(fromParcel.getPreference().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ assertThat(((MultiTogglePreference) fromParcel.getPreference()).getTitle())
+ .isEqualTo("multi_toggle_preference");
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_unknownPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(new DeviceSettingPreference(123) {})
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isSameInstanceAs(DeviceSettingPreference.UNKNOWN);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceSetting writeAndRead(DeviceSetting state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceSetting fromParcel = DeviceSetting.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
new file mode 100644
index 0000000..2b29a6e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingsConfigTest {
+
+ @Test
+ fun parcelOperation() {
+ val config =
+ DeviceSettingsConfig(
+ mainContentItems =
+ listOf(
+ DeviceSettingItem(
+ 1,
+ "package_name_1",
+ "class_name_1",
+ "intent_action_1",
+ Bundle()
+ )
+ ),
+ moreSettingsItems =
+ listOf(
+ DeviceSettingItem(
+ 2,
+ "package_name_2",
+ "class_name_2",
+ "intent_action_2",
+ Bundle()
+ )
+ ),
+ moreSettingsFooter = "footer",
+ extras = Bundle().apply { putString("key1", "value1") },
+ )
+
+ val fromParcel = writeAndRead(config)
+
+ assertThat(fromParcel.mainContentItems.stream().map { it.settingId }.toList())
+ .containsExactly(1)
+ assertThat(fromParcel.mainContentItems.stream().map { it.packageName }.toList())
+ .containsExactly("package_name_1")
+ assertThat(fromParcel.mainContentItems.stream().map { it.className }.toList())
+ .containsExactly("class_name_1")
+ assertThat(fromParcel.mainContentItems.stream().map { it.intentAction }.toList())
+ .containsExactly("intent_action_1")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.settingId }.toList())
+ .containsExactly(2)
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.packageName }.toList())
+ .containsExactly("package_name_2")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.className }.toList())
+ .containsExactly("class_name_2")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.intentAction }.toList())
+ .containsExactly("intent_action_2")
+ assertThat(fromParcel.moreSettingsFooter).isEqualTo(config.moreSettingsFooter)
+ }
+
+ private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
+ val parcel = Parcel.obtain()
+ item.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return DeviceSettingsConfig.CREATOR.createFromParcel(parcel)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java
new file mode 100644
index 0000000..2645fc5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MultiTogglePreferenceStateTest {
+
+ @Test
+ public void getMethods() {
+ MultiTogglePreferenceState state1 =
+ new MultiTogglePreferenceState.Builder()
+ .setState(1)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ MultiTogglePreferenceState state2 =
+ new MultiTogglePreferenceState.Builder()
+ .setState(2)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ assertThat(state1.getState()).isEqualTo(1);
+ assertThat(state2.getState()).isEqualTo(2);
+ assertThat(state1.getExtras().getString("key1")).isEqualTo("value1");
+ assertThat(state2.getExtras().getString("key2")).isEqualTo("value2");
+ }
+
+ @Test
+ public void parcelOperation() {
+ MultiTogglePreferenceState state =
+ new MultiTogglePreferenceState.Builder()
+ .setState(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ MultiTogglePreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getState()).isEqualTo(state.getState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private MultiTogglePreferenceState writeAndRead(MultiTogglePreferenceState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MultiTogglePreferenceState fromParcel =
+ MultiTogglePreferenceState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
new file mode 100644
index 0000000..62fcb5e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MultiTogglePreferenceTest {
+ private static final Bitmap ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ private static final ToggleInfo TOGGLE_INFO_1 =
+ new ToggleInfo.Builder().setLabel("label1").setIcon(ICON).build();
+ private static final ToggleInfo TOGGLE_INFO_2 =
+ new ToggleInfo.Builder().setLabel("label2").setIcon(ICON).build();
+
+ @Test
+ public void build_withoutTitle_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .addToggleInfo(TOGGLE_INFO_1)
+ .setState(0)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withNegativeState_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .setState(-1)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ MultiTogglePreference preference =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(preference.getTitle()).isEqualTo("title");
+ assertThat(preference.getToggleInfos().stream().map(ToggleInfo::getLabel).toList())
+ .containsExactly("label1", "label2");
+ assertThat(preference.getState()).isEqualTo(123);
+ assertThat(preference.isAllowedChangingState()).isTrue();
+ assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ MultiTogglePreference preference =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ MultiTogglePreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getToggleInfos().stream().map(ToggleInfo::getLabel).toList())
+ .containsExactly("label1", "label2");
+ assertThat(fromParcel.getState()).isEqualTo(preference.getState());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private MultiTogglePreference writeAndRead(MultiTogglePreference preference) {
+ Parcel parcel = Parcel.obtain();
+ preference.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MultiTogglePreference fromParcel = MultiTogglePreference.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java
new file mode 100644
index 0000000..439749a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ToggleInfoTest {
+
+ @Test
+ public void build_withoutIcon_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutLabel_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ToggleInfo info =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(icon)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(info.getLabel()).isEqualTo("label");
+ assertThat(info.getIcon()).isSameInstanceAs(icon);
+ assertThat(info.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ToggleInfo info =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(icon)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ ToggleInfo fromParcel = writeAndRead(info);
+
+ assertThat(fromParcel.getLabel()).isEqualTo(info.getLabel());
+ assertThat(fromParcel.getIcon().sameAs(info.getIcon())).isTrue();
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(info.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ToggleInfo writeAndRead(ToggleInfo state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ToggleInfo fromParcel = ToggleInfo.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index 80301c0..b143b22 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -28,23 +28,31 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
+import com.android.settingslib.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import java.util.List;
@@ -54,26 +62,22 @@
private static final int BATTERY_SAVER_THRESHOLD_1 = 15;
private static final int BATTERY_SAVER_THRESHOLD_2 = 20;
- @Mock
- private Context mMockContext;
+ @Rule(order = 0) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule(order = 1) public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Mock
- private ContentResolver mMockResolver;
-
- @Mock
- private PowerManager mMockPowerManager;
+ @Mock private Context mMockContext;
+ @Mock private ContentResolver mMockResolver;
+ @Mock private PowerManager mMockPowerManager;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
when(mMockContext.getSystemService(eq(PowerManager.class))).thenReturn(mMockPowerManager);
when(mMockPowerManager.setPowerSaveModeEnabled(anyBoolean())).thenReturn(true);
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_firstCall_needConfirmationWarning() {
+ public void setPowerSaveMode_enableWithWarning_firstCall_needConfirmationWarning() {
Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -96,7 +100,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_secondCall_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithWarning_secondCall_expectUpdateIntent() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -119,7 +123,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_thirdCall_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithWarning_thirdCall_expectUpdateIntent() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -142,7 +146,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithWarning_5thCall_needAutoSuggestionWarning() {
+ public void setPowerSaveMode_enableWithWarning_5thCall_needAutoSuggestionWarning() {
// Already acked.
Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
@@ -166,7 +170,7 @@
}
@Test
- public void testSetPowerSaveMode_enableWithoutWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_enableWithoutWarning_expectUpdateIntent() {
Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
@@ -187,17 +191,17 @@
}
@Test
- public void testSetPowerSaveMode_disableWithoutWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_disableWithoutWarning_expectUpdateIntent() {
verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ false);
}
@Test
- public void testSetPowerSaveMode_disableWithWarning_expectUpdateIntent() {
+ public void setPowerSaveMode_disableWithWarning_expectUpdateIntent() {
verifyDisablePowerSaveMode(/* needFirstTimeWarning= */ true);
}
@Test
- public void testEnsureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() {
+ public void ensureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() {
Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
BatterySaverUtils.ensureAutoBatterySaver(mMockContext, BATTERY_SAVER_THRESHOLD_1);
@@ -212,7 +216,7 @@
}
@Test
- public void testSetAutoBatterySaverTriggerLevel_setSuppressSuggestion() {
+ public void setAutoBatterySaverTriggerLevel_setSuppressSuggestion() {
Global.putString(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, "null");
Secure.putString(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, "null");
@@ -230,7 +234,7 @@
}
@Test
- public void testGetBatterySaverScheduleKey_returnExpectedKey() {
+ public void getBatterySaverScheduleKey_returnExpectedKey() {
Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
@@ -253,8 +257,25 @@
KEY_NO_SCHEDULE);
}
+ @EnableFlags(Flags.FLAG_EXTREME_POWER_LOW_STATE_VULNERABILITY)
@Test
- public void testSetBatterySaverScheduleMode_setSchedule() {
+ public void setPowerSaveMode_1stTimeAndDeviceLocked_enableBatterySaver() {
+ var keyguardManager = mock(KeyguardManager.class);
+ when(mMockContext.getSystemService(KeyguardManager.class)).thenReturn(keyguardManager);
+ when(keyguardManager.isDeviceLocked()).thenReturn(true);
+ when(mMockPowerManager.setPowerSaveModeEnabled(true)).thenReturn(true);
+
+ var enableResult = BatterySaverUtils.setPowerSaveMode(
+ mMockContext,
+ /* enable= */ true,
+ /* needFirstTimeWarning= */ true,
+ /* reason= */ 0);
+
+ assertThat(enableResult).isTrue();
+ }
+
+ @Test
+ public void setBatterySaverScheduleMode_setSchedule() {
BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_NO_SCHEDULE, -1);
assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 861c405..fd4fc20 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -740,8 +740,6 @@
// The settings provider must hold its lock when calling here.
@GuardedBy("mLock")
public void removeSettingsForPackageLocked(String packageName) {
- boolean removedSomething = false;
-
final int settingCount = mSettings.size();
for (int i = settingCount - 1; i >= 0; i--) {
String name = mSettings.keyAt(i);
@@ -752,14 +750,9 @@
}
Setting setting = mSettings.valueAt(i);
if (packageName.equals(setting.packageName)) {
- mSettings.removeAt(i);
- removedSomething = true;
+ deleteSettingLocked(setting.name);
}
}
-
- if (removedSomething) {
- scheduleWriteIfNeededLocked();
- }
}
// The settings provider must hold its lock when calling here.
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 4b4ced3..48ce49d 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -988,7 +988,40 @@
}
@Test
- public void testGetFlagOverrideToSync() {
+ public void testMemoryUsagePerPackage_StatsUpdatedOnAppDataCleared() {
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ final String testKey1 = SETTING_NAME;
+ final String testKey2 = SETTING_NAME + "_2";
+ final String testValue1 = Strings.repeat("A", 9000);
+ final String testValue2 = Strings.repeat("A", 9001);
+ final String packageName = "p";
+ // Inserting the first setting should be okay
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, packageName);
+ int expectedMemUsageForPackage = (testKey1.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName));
+ // Inserting the second setting should fail
+ try {
+ settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName);
+ fail("Should throw because it exceeded max memory usage per package");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().startsWith("You are adding too many system settings."));
+ }
+ // Now clear app data and check that the memory usage is cleared
+ settingsState.removeSettingsForPackageLocked(packageName);
+ assertEquals(0, settingsState.getMemoryUsage(packageName));
+ // Try inserting the second setting again and it should go through
+ settingsState.insertSettingLocked(testKey2, testValue2, null, true, packageName);
+ expectedMemUsageForPackage = (testKey2.length() + testValue2.length()
+ + testValue2.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage, settingsState.getMemoryUsage(packageName));
+ }
+
+ @Test
+ public void testGetFlagOverrideToSync() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
SettingsState settingsState =
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3f165a3..a1f1a08 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -824,13 +824,6 @@
}
flag {
- name: "media_controls_refactor"
- namespace: "systemui"
- description: "Refactors media code to follow the recommended architecture"
- bug: "326408371"
-}
-
-flag {
name: "qs_tile_focus_state"
namespace: "systemui"
description: "enables new focus outline for qs tiles when focused on with physical keyboard"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 97ed74f..b7d6e09 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -658,14 +658,18 @@
)
.onSizeChanged { setToolbarSize(it) },
) {
+ val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
ToolbarButton(
isPrimary = !removeEnabled,
- modifier = Modifier.align(Alignment.CenterStart),
+ modifier =
+ Modifier.align(Alignment.CenterStart).semantics {
+ contentDescription = addWidgetText
+ },
onClick = onOpenWidgetPicker,
) {
- Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
+ Icon(Icons.Default.Add, null)
Text(
- text = stringResource(R.string.hub_mode_add_widget_button_text),
+ text = addWidgetText,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 887e349..25e91be 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable
+import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@@ -48,6 +49,16 @@
fun SceneScope.Content(
modifier: Modifier = Modifier,
) {
+ val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
+ if (!isContentVisible) {
+ // If the content isn't supposed to be visible, show a large empty box as it's needed
+ // for scene transition animations (can't just skip rendering everything or shared
+ // elements won't have correct final/initial bounds from animating in and out of the
+ // lockscreen scene).
+ Box(modifier)
+ return
+ }
+
val coroutineScope = rememberCoroutineScope()
val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
val view = LocalView.current
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
deleted file mode 100644
index 07d8890..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2024 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.ambient.touch;
-
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-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.DreamManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ShadeTouchHandlerTest extends SysuiTestCase {
- @Mock
- CentralSurfaces mCentralSurfaces;
-
- @Mock
- ShadeViewController mShadeViewController;
-
- @Mock
- DreamManager mDreamManager;
-
- @Mock
- TouchHandler.TouchSession mTouchSession;
-
- ShadeTouchHandler mTouchHandler;
-
- @Captor
- ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor;
- @Captor
- ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor;
-
- private static final int TOUCH_HEIGHT = 20;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
- mDreamManager, TOUCH_HEIGHT);
- }
-
- // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
- @Test
- public void testSwipeDown_captured() {
- final boolean captured = swipe(Direction.DOWN);
-
- assertThat(captured).isTrue();
- }
-
- // Verifies that a swipe in the upward direction is not captured.
- @Test
- public void testSwipeUp_notCaptured() {
- final boolean captured = swipe(Direction.UP);
-
- // Motion events not captured as the swipe is going in the wrong direction.
- assertThat(captured).isFalse();
- }
-
- // Verifies that a swipe down forwards captured touches to central surfaces for handling.
- @Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
- public void testSwipeDown_communalEnabled_sentToCentralSurfaces() {
- swipe(Direction.DOWN);
-
- // Both motion events are sent for central surfaces to process.
- verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any());
- }
-
- // Verifies that a swipe down forwards captured touches to the shade view for handling.
- @Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
- public void testSwipeDown_communalDisabled_sentToShadeView() {
- swipe(Direction.DOWN);
-
- // Both motion events are sent for the shade view to process.
- verify(mShadeViewController, times(2)).handleExternalTouch(any());
- }
-
- // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
- // handling.
- @Test
- public void testSwipeDown_dreaming_sentToShadeView() {
- when(mDreamManager.isDreaming()).thenReturn(true);
-
- swipe(Direction.DOWN);
-
- // Both motion events are sent for the shade view to process.
- verify(mShadeViewController, times(2)).handleExternalTouch(any());
- }
-
- // Verifies that a swipe up is not forwarded to central surfaces.
- @Test
- @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
- public void testSwipeUp_communalEnabled_touchesNotSent() {
- swipe(Direction.UP);
-
- // Motion events are not sent for central surfaces to process as the swipe is going in the
- // wrong direction.
- verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any());
- }
-
- // Verifies that a swipe up is not forwarded to the shade view.
- @Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
- public void testSwipeUp_communalDisabled_touchesNotSent() {
- swipe(Direction.UP);
-
- // Motion events are not sent for the shade view to process as the swipe is going in the
- // wrong direction.
- verify(mShadeViewController, never()).handleExternalTouch(any());
- }
-
- /**
- * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
- * touch handler's gesture listener.
- * <p>
- * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
- * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0.
- */
- private boolean swipe(Direction direction) {
- Mockito.clearInvocations(mTouchSession);
- mTouchHandler.onSessionStart(mTouchSession);
-
- verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture());
- verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture());
-
- final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0;
- final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT;
-
- // Send touches to the input and gesture listener.
- final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0);
- final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0);
- mInputListenerCaptor.getValue().onInputEvent(event1);
- mInputListenerCaptor.getValue().onInputEvent(event2);
- final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0,
- startY - endY);
-
- return captured;
- }
-
- private enum Direction {
- DOWN, UP,
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
new file mode 100644
index 0000000..4314676
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 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.ambient.touch
+
+import android.app.DreamManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.GestureDetector
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.google.common.truth.Truth
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeTouchHandlerTest : SysuiTestCase() {
+ private var mCentralSurfaces = mock<CentralSurfaces>()
+ private var mShadeViewController = mock<ShadeViewController>()
+ private var mDreamManager = mock<DreamManager>()
+ private var mTouchSession = mock<TouchSession>()
+
+ private lateinit var mTouchHandler: ShadeTouchHandler
+
+ private var mGestureListenerCaptor = argumentCaptor<GestureDetector.OnGestureListener>()
+ private var mInputListenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>()
+
+ @Before
+ fun setup() {
+ mTouchHandler =
+ ShadeTouchHandler(
+ Optional.of(mCentralSurfaces),
+ mShadeViewController,
+ mDreamManager,
+ TOUCH_HEIGHT
+ )
+ }
+
+ // Verifies that a swipe down in the gesture region is captured by the shade touch handler.
+ @Test
+ fun testSwipeDown_captured() {
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isTrue()
+ }
+
+ // Verifies that a swipe in the upward direction is not captured.
+ @Test
+ fun testSwipeUp_notCaptured() {
+ val captured = swipe(Direction.UP)
+
+ // Motion events not captured as the swipe is going in the wrong direction.
+ Truth.assertThat(captured).isFalse()
+ }
+
+ // Verifies that a swipe down forwards captured touches to central surfaces for handling.
+ @Test
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
+ swipe(Direction.DOWN)
+
+ // Both motion events are sent for central surfaces to process.
+ verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any())
+ }
+
+ // Verifies that a swipe down forwards captured touches to the shade view for handling.
+ @Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun testSwipeDown_communalDisabled_sentToShadeView() {
+ swipe(Direction.DOWN)
+
+ // Both motion events are sent for the shade view to process.
+ verify(mShadeViewController, times(2)).handleExternalTouch(any())
+ }
+
+ // Verifies that a swipe down while dreaming forwards captured touches to the shade view for
+ // handling.
+ @Test
+ fun testSwipeDown_dreaming_sentToShadeView() {
+ whenever(mDreamManager.isDreaming).thenReturn(true)
+ swipe(Direction.DOWN)
+
+ // Both motion events are sent for the shade view to process.
+ verify(mShadeViewController, times(2)).handleExternalTouch(any())
+ }
+
+ // Verifies that a swipe up is not forwarded to central surfaces.
+ @Test
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun testSwipeUp_communalEnabled_touchesNotSent() {
+ swipe(Direction.UP)
+
+ // Motion events are not sent for central surfaces to process as the swipe is going in the
+ // wrong direction.
+ verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any())
+ }
+
+ // Verifies that a swipe up is not forwarded to the shade view.
+ @Test
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun testSwipeUp_communalDisabled_touchesNotSent() {
+ swipe(Direction.UP)
+
+ // Motion events are not sent for the shade view to process as the swipe is going in the
+ // wrong direction.
+ verify(mShadeViewController, never()).handleExternalTouch(any())
+ }
+
+ /**
+ * Simulates a swipe in the given direction and returns true if the touch was intercepted by the
+ * touch handler's gesture listener.
+ *
+ * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge
+ * of the gesture region, [.TOUCH_HEIGHT], and goes upward to 0.
+ */
+ private fun swipe(direction: Direction): Boolean {
+ clearInvocations(mTouchSession)
+ mTouchHandler.onSessionStart(mTouchSession)
+ verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture())
+ verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture())
+ val startY = (if (direction == Direction.UP) TOUCH_HEIGHT else 0).toFloat()
+ val endY = (if (direction == Direction.UP) 0 else TOUCH_HEIGHT).toFloat()
+
+ // Send touches to the input and gesture listener.
+ val event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, startY, 0)
+ val event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0f, endY, 0)
+ mInputListenerCaptor.lastValue.onInputEvent(event1)
+ mInputListenerCaptor.lastValue.onInputEvent(event2)
+ return mGestureListenerCaptor.lastValue.onScroll(event1, event2, 0f, startY - endY)
+ }
+
+ private enum class Direction {
+ DOWN,
+ UP
+ }
+
+ companion object {
+ private const val TOUCH_HEIGHT = 20
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index de4b999..875e9e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.flag.junit.FlagsParameterization
@@ -27,10 +29,13 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
@@ -38,6 +43,8 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -201,6 +208,44 @@
}
}
+ @Test
+ fun isContentVisible_whenNotOccluded_visible() =
+ with(kosmos) {
+ testScope.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
+ runCurrent()
+ assertThat(isContentVisible).isTrue()
+ }
+ }
+
+ @Test
+ fun isContentVisible_whenOccluded_notVisible() =
+ with(kosmos) {
+ testScope.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+ runCurrent()
+ assertThat(isContentVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() =
+ with(kosmos) {
+ testScope.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+ runCurrent()
+
+ sceneInteractor.snapToScene(Scenes.Shade, "")
+ runCurrent()
+ assertThat(isContentVisible).isFalse()
+ }
+ }
+
private fun prepareConfiguration(): Int {
val configuration = context.resources.configuration
configuration.setLayoutDirection(Locale.US)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ca5fc12..0bc2c82 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -341,13 +341,17 @@
<string name="share_to_app_stop_dialog_button">Stop sharing</string>
<!-- Content description for the status bar chip shown to the user when they're casting their screen to a different device [CHAR LIMIT=NONE] -->
- <string name="cast_to_other_device_chip_accessibility_label">Casting screen</string>
+ <string name="cast_screen_to_other_device_chip_accessibility_label">Casting screen</string>
<!-- Title for a dialog shown to the user that will let them stop casting their screen to a different device [CHAR LIMIT=50] -->
- <string name="cast_to_other_device_stop_dialog_title">Stop casting screen?</string>
+ <string name="cast_screen_to_other_device_stop_dialog_title">Stop casting screen?</string>
+ <!-- Title for a dialog shown to the user that will let them stop casting to a different device [CHAR LIMIT=50] -->
+ <string name="cast_to_other_device_stop_dialog_title">Stop casting?</string>
<!-- Text telling a user that they will stop casting their screen to a different device if they click the "Stop casting" button [CHAR LIMIT=100] -->
- <string name="cast_to_other_device_stop_dialog_message">You will stop casting your screen</string>
+ <string name="cast_screen_to_other_device_stop_dialog_message">You will stop casting your screen</string>
<!-- Text telling a user that they will stop casting the contents of the specified [app_name] to a different device if they click the "Stop casting" button. Note that the app name will appear in bold. [CHAR LIMIT=100] -->
- <string name="cast_to_other_device_stop_dialog_message_specific_app">You will stop casting <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
+ <string name="cast_screen_to_other_device_stop_dialog_message_specific_app">You will stop casting <b><xliff:g id="app_name" example="Photos App">%1$s</xliff:g></b></string>
+ <!-- Text telling a user that they're currently casting to a different device [CHAR LIMIT=100] -->
+ <string name="cast_to_other_device_stop_dialog_message">You\'re currently casting</string>
<!-- Button to stop screen casting to a different device [CHAR LIMIT=35] -->
<string name="cast_to_other_device_stop_dialog_button">Stop casting</string>
@@ -1231,6 +1235,10 @@
<string name="accessibility_action_label_remove_widget">remove widget</string>
<!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_place_widget">place selected widget</string>
+ <!-- Title in the communal widget picker. [CHAR LIMIT=50] -->
+ <string name="communal_widget_picker_title">Lock screen widgets</string>
+ <!-- Text displayed below the title in the communal widget picker providing additional details about the communal surface. [CHAR LIMIT=80] -->
+ <string name="communal_widget_picker_description">Anyone can view widgets on your lock screen, even if your tablet\'s locked.</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d30f33f..a67dcdb 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -515,7 +515,7 @@
}
@Nullable
- private ComponentName getAssistInfo() {
+ public ComponentName getAssistInfo() {
return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 2e92438..4f54fee 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -187,6 +187,11 @@
CommunalWidgetCategories.defaultCategories
)
putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE)
+ putExtra(EXTRA_PICKER_TITLE, resources.getString(R.string.communal_widget_picker_title))
+ putExtra(
+ EXTRA_PICKER_DESCRIPTION,
+ resources.getString(R.string.communal_widget_picker_description)
+ )
putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList)
}
}
@@ -214,6 +219,8 @@
private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width"
private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height"
+ private const val EXTRA_PICKER_TITLE = "picker_title"
+ private const val EXTRA_PICKER_DESCRIPTION = "picker_description"
private const val EXTRA_UI_SURFACE_KEY = "ui_surface"
private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub"
const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4b2fb44..572283a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -74,6 +74,7 @@
import com.android.systemui.mediaprojection.MediaProjectionModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
+import com.android.systemui.mediarouter.MediaRouterModule;
import com.android.systemui.model.SceneContainerPlugin;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
@@ -220,6 +221,7 @@
MediaProjectionActivitiesModule.class,
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,
+ MediaRouterModule.class,
MotionToolModule.class,
NotificationIconAreaControllerModule.class,
PeopleHubModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 5b9d30e..b703892 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -227,7 +227,7 @@
get() =
when (type) {
ShortcutCategoryType.SYSTEM -> R.string.shortcut_helper_category_system
- ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_system
+ ShortcutCategoryType.MULTI_TASKING -> R.string.shortcut_helper_category_multitasking
ShortcutCategoryType.IME -> R.string.shortcut_helper_category_input
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index 646db40..d0e3ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -34,7 +34,6 @@
import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.PlatformTheme
import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
import com.android.systemui.res.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
@@ -81,10 +80,7 @@
requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
setContent {
PlatformTheme {
- val shortcutsUiState by
- viewModel.shortcutsUiState.collectAsStateWithLifecycle(
- initialValue = ShortcutsUiState.Inactive
- )
+ val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle()
ShortcutHelper(
shortcutsUiState = shortcutsUiState,
onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 3759b0c..e602cad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -23,13 +23,17 @@
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
class ShortcutHelperViewModel
@Inject
constructor(
+ @Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val stateInteractor: ShortcutHelperStateInteractor,
categoriesInteractor: ShortcutHelperCategoriesInteractor,
@@ -42,16 +46,22 @@
.flowOn(backgroundDispatcher)
val shortcutsUiState =
- categoriesInteractor.shortcutCategories.map {
- if (it.isEmpty()) {
- ShortcutsUiState.Inactive
- } else {
- ShortcutsUiState.Active(
- shortcutCategories = it,
- defaultSelectedCategory = it.first().type,
- )
+ categoriesInteractor.shortcutCategories
+ .map {
+ if (it.isEmpty()) {
+ ShortcutsUiState.Inactive
+ } else {
+ ShortcutsUiState.Active(
+ shortcutCategories = it,
+ defaultSelectedCategory = it.first().type,
+ )
+ }
}
- }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ initialValue = ShortcutsUiState.Inactive
+ )
fun onViewClosed() {
stateInteractor.onViewClosed()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index b32d0950..d1a8463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -103,14 +103,14 @@
* swiped away via a touch gesture, or when it's flinging expanded/collapsed after a swipe.
*/
const val LEGACY_UNLOCK_ANIMATION_DURATION_MS = 200L
-const val UNLOCK_ANIMATION_DURATION_MS = 167L
+const val UNLOCK_ANIMATION_DURATION_MS = 300L
/**
* If there are two different wallpapers on home and lock screen, duration and delay of the lock
* wallpaper fade out.
*/
-const val LOCK_WALLPAPER_FADE_OUT_DURATION = 140L
-const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 0L
+const val LOCK_WALLPAPER_FADE_OUT_DURATION = 150L
+const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 150L
/**
* How long the in-window launcher icon animation takes. This is used if the launcher is underneath
@@ -167,7 +167,7 @@
private val statusBarStateController: SysuiStatusBarStateController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -380,7 +380,6 @@
else LAUNCHER_ICONS_ANIMATION_DURATION_MS
interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
else Interpolators.ALPHA_OUT
- if (fasterUnlockTransition()) startDelay = CANNED_UNLOCK_START_DELAY
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
valueAnimator.animatedValue as Float, openingWallpaperTargets)
@@ -647,14 +646,12 @@
val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
- val duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- duration /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -746,6 +743,10 @@
// As soon as the shade starts animating out of the way, start the canned unlock animation,
// which will finish keyguard exit when it completes. The in-window animations in the
// Launcher window will end on their own.
+ if (fasterUnlockTransition() && openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeOutWallpaper()
+ }
+
handler.postDelayed({
if (keyguardViewMediator.get().isShowingAndNotOccluded &&
!keyguardStateController.isKeyguardGoingAway) {
@@ -754,9 +755,8 @@
return@postDelayed
}
- if ((openingWallpaperTargets?.isNotEmpty() == true)) {
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
fadeInWallpaper()
- if (fasterUnlockTransition()) fadeOutWallpaper()
hideKeyguardViewAfterRemoteAnimation()
} else {
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
@@ -1038,6 +1038,7 @@
surfaceBehindAlphaAnimator.cancel()
surfaceBehindEntryAnimator.cancel()
wallpaperCannedUnlockAnimator.cancel()
+ if (fasterUnlockTransition()) wallpaperFadeOutUnlockAnimator.cancel()
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 1de0abe..8a29f96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -48,6 +49,7 @@
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ occlusionInteractor: SceneContainerOcclusionInteractor,
) {
@VisibleForTesting val clockSize = clockInteractor.clockSize
@@ -93,6 +95,16 @@
initialValue = UnfoldTranslations(),
)
+ /** Whether the content of the scene UI should be shown. */
+ val isContentVisible: StateFlow<Boolean> =
+ occlusionInteractor.isOccludingActivityShown
+ .map { !it }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = true,
+ )
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
new file mode 100644
index 0000000..c07e3a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.mediarouter
+
+import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.mediarouter.data.repository.MediaRouterRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaRouterModule {
+ @Binds fun mediaRouterRepository(impl: MediaRouterRepositoryImpl): MediaRouterRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
new file mode 100644
index 0000000..998d76c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.mediarouter.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.statusbar.policy.CastDevice
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository for data coming from MediaRouter APIs. */
+interface MediaRouterRepository {
+ /** A list of the cast devices that MediaRouter is currently aware of. */
+ val castDevices: StateFlow<List<CastDevice>>
+
+ /** Stops the cast to the given device. */
+ fun stopCasting(device: CastDevice)
+}
+
+@SysUISingleton
+class MediaRouterRepositoryImpl
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val castController: CastController,
+) : MediaRouterRepository {
+ override val castDevices: StateFlow<List<CastDevice>> =
+ conflatedCallbackFlow {
+ val callback =
+ CastController.Callback {
+ val mediaRouterCastDevices =
+ castController.castDevices.filter {
+ it.origin == CastDevice.CastOrigin.MediaRouter
+ }
+ trySend(mediaRouterCastDevices)
+ }
+ castController.addCallback(callback)
+ awaitClose { castController.removeCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override fun stopCasting(device: CastDevice) {
+ castController.stopCasting(device)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4e2e227..b3624ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -115,7 +115,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
import dagger.Lazy;
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4a4c73b..98a61df 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -74,7 +74,7 @@
when (intent?.action) {
ACTION_START -> {
bgExecutor.execute {
- traceurMessageSender.startTracing(issueRecordingState.traceType)
+ traceurMessageSender.startTracing(issueRecordingState.traceConfig)
}
issueRecordingState.isRecording = true
if (!issueRecordingState.recordScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index b077349..7612900 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -22,7 +22,8 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.traceur.TraceUtils.PresetTraceType
+import com.android.traceur.PresetTraceConfigs
+import com.android.traceur.TraceConfig
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@@ -53,8 +54,8 @@
get() = prefs.getInt(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()
- val traceType: PresetTraceType
- get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceType.UNSET
+ val traceConfig: TraceConfig
+ get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceConfigs.getDefaultConfig()
private val listeners = CopyOnWriteArrayList<Runnable>()
@@ -83,12 +84,12 @@
const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
const val ISSUE_TYPE_NOT_SET = -1
- val ALL_ISSUE_TYPES: Map<Int, PresetTraceType> =
+ val ALL_ISSUE_TYPES: Map<Int, TraceConfig?> =
hashMapOf(
- Pair(R.string.performance, PresetTraceType.PERFORMANCE),
- Pair(R.string.user_interface, PresetTraceType.UI),
- Pair(R.string.battery, PresetTraceType.BATTERY),
- Pair(R.string.thermal, PresetTraceType.THERMAL)
+ Pair(R.string.performance, PresetTraceConfigs.getPerformanceConfig()),
+ Pair(R.string.user_interface, PresetTraceConfigs.getUiConfig()),
+ Pair(R.string.battery, PresetTraceConfigs.getBatteryConfig()),
+ Pair(R.string.thermal, PresetTraceConfigs.getThermalConfig()),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index 51744aa..903d662 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -35,7 +35,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.traceur.FileSender
import com.android.traceur.MessageConstants
-import com.android.traceur.TraceUtils.PresetTraceType
+import com.android.traceur.TraceConfig
import javax.inject.Inject
private const val TAG = "TraceurMessageSender"
@@ -93,9 +93,9 @@
}
@WorkerThread
- fun startTracing(traceType: PresetTraceType) {
+ fun startTracing(traceType: TraceConfig) {
val data =
- Bundle().apply { putSerializable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
+ Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
notifyTraceur(MessageConstants.START_WHAT, data)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
index 233e9b5..2d510e1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -43,8 +43,14 @@
sceneInteractor: SceneInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
- /** Whether a show-when-locked activity is at the top of the current activity stack. */
- private val isOccludingActivityShown: StateFlow<Boolean> =
+ /**
+ * Whether a show-when-locked activity is at the top of the current activity stack.
+ *
+ * Note: this isn't enough to figure out whether the scene container UI should be invisible as
+ * that also depends on the things like the state of AOD and the current scene. If the code
+ * needs that, [invisibleDueToOcclusion] should be collected instead.
+ */
+ val isOccludingActivityShown: StateFlow<Boolean> =
keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
@@ -69,6 +75,9 @@
/**
* Whether the scene container should become invisible due to "occlusion" by an in-foreground
* "show when locked" activity.
+ *
+ * Note: this returns `false` when an overlaid scene (like shade or QS) is shown above the
+ * occluding activity.
*/
val invisibleDueToOcclusion: StateFlow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
new file mode 100644
index 0000000..21f301c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.statusbar.chips.casttootherdevice.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
+import com.android.systemui.statusbar.policy.CastDevice
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Interactor for MediaRouter events, used to show the cast-audio-to-other-device chip in the status
+ * bar.
+ */
+@SysUISingleton
+class MediaRouterChipInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val mediaRouterRepository: MediaRouterRepository,
+) {
+ private val activeCastDevice: StateFlow<CastDevice?> =
+ mediaRouterRepository.castDevices
+ .map { allDevices -> allDevices.firstOrNull { it.isCasting } }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ /** The current casting state, according to MediaRouter APIs. */
+ val mediaRouterCastingState: StateFlow<MediaRouterCastModel> =
+ activeCastDevice
+ .map {
+ if (it != null) {
+ MediaRouterCastModel.Casting
+ } else {
+ MediaRouterCastModel.DoingNothing
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MediaRouterCastModel.DoingNothing)
+
+ /** Stops the currently active MediaRouter cast. */
+ fun stopCasting() {
+ activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
new file mode 100644
index 0000000..b228922
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/model/MediaRouterCastModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.statusbar.chips.casttootherdevice.domain.model
+
+/** Represents the current casting state, according to MediaRouter APIs. */
+sealed interface MediaRouterCastModel {
+ /** MediaRouter isn't aware of any active cast. */
+ data object DoingNothing : MediaRouterCastModel
+
+ /** MediaRouter has an active cast. */
+ data object Casting : MediaRouterCastModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index bc0f492..ffb20a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -24,7 +24,7 @@
import com.android.systemui.statusbar.phone.SystemUIDialog
/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
-class EndCastToOtherDeviceDialogDelegate(
+class EndCastScreenToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val stopAction: () -> Unit,
private val state: ProjectionChipModel.Projecting,
@@ -36,13 +36,14 @@
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
- setTitle(R.string.cast_to_other_device_stop_dialog_title)
+ setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
+ // TODO(b/332662551): Include device name in this string.
setMessage(
endMediaProjectionDialogHelper.getDialogMessage(
state.projectionState,
- genericMessageResId = R.string.cast_to_other_device_stop_dialog_message,
+ genericMessageResId = R.string.cast_screen_to_other_device_stop_dialog_message,
specificAppMessageResId =
- R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
)
// No custom on-click, because the dialog will automatically be dismissed when the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
similarity index 74%
copy from packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index bc0f492..afe67b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -19,15 +19,17 @@
import android.os.Bundle
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
-import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.phone.SystemUIDialog
-/** A dialog that lets the user stop an ongoing cast-screen-to-other-device event. */
-class EndCastToOtherDeviceDialogDelegate(
+/**
+ * A dialog that lets the user stop an ongoing cast-to-other-device event. The user could be casting
+ * their screen, or just casting their audio. This dialog uses generic strings to handle both cases
+ * well.
+ */
+class EndGenericCastToOtherDeviceDialogDelegate(
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val stopAction: () -> Unit,
- private val state: ProjectionChipModel.Projecting,
) : SystemUIDialog.Delegate {
override fun createDialog(): SystemUIDialog {
return endMediaProjectionDialogHelper.createDialog(this)
@@ -37,14 +39,8 @@
with(dialog) {
setIcon(CAST_TO_OTHER_DEVICE_ICON)
setTitle(R.string.cast_to_other_device_stop_dialog_title)
- setMessage(
- endMediaProjectionDialogHelper.getDialogMessage(
- state.projectionState,
- genericMessageResId = R.string.cast_to_other_device_stop_dialog_message,
- specificAppMessageResId =
- R.string.cast_to_other_device_stop_dialog_message_specific_app,
- )
- )
+ // TODO(b/332662551): Include device name in this string.
+ setMessage(R.string.cast_to_other_device_stop_dialog_message)
// No custom on-click, because the dialog will automatically be dismissed when the
// button is clicked anyway.
setNegativeButton(R.string.close_dialog_button, /* onClick= */ null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 73ccaab..2eff336 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -23,7 +23,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
+import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndGenericCastToOtherDeviceDialogDelegate
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -36,12 +39,14 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
- * View model for the cast-to-other-device chip, shown when sharing your phone screen content to a
- * different device. (Triggered from the Quick Settings Cast tile or from the Settings app.)
+ * View model for the cast-to-other-device chip, shown when a user is sharing content to a different
+ * device. (Triggered from the Quick Settings Cast tile or from the Settings app.) The content could
+ * either be the user's screen, or just the user's audio.
*/
@SysUISingleton
class CastToOtherDeviceChipViewModel
@@ -49,14 +54,19 @@
constructor(
@Application private val scope: CoroutineScope,
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
+ private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
) : OngoingActivityChipViewModel {
- override val chip: StateFlow<OngoingActivityChipModel> =
- // TODO(b/342169876): The MediaProjection APIs are not invoked for certain
- // cast-to-other-device events, like audio-only casting. We should also listen to
- // MediaRouter APIs to cover all cast events.
+ /**
+ * The cast chip to show, based only on MediaProjection API events.
+ *
+ * This chip will only be [OngoingActivityChipModel.Shown] when the user is casting their
+ * *screen*. If the user is only casting audio, this chip will be
+ * [OngoingActivityChipModel.Hidden].
+ */
+ private val projectionChip: StateFlow<OngoingActivityChipModel> =
mediaProjectionChipInteractor.projection
.map { projectionModel ->
when (projectionModel) {
@@ -65,7 +75,7 @@
if (projectionModel.type != ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE) {
OngoingActivityChipModel.Hidden
} else {
- createCastToOtherDeviceChip(projectionModel)
+ createCastScreenToOtherDeviceChip(projectionModel)
}
}
}
@@ -73,43 +83,132 @@
// See b/347726238.
.stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden)
+ /**
+ * The cast chip to show, based only on MediaRouter API events.
+ *
+ * This chip will be [OngoingActivityChipModel.Shown] when the user is casting their screen *or*
+ * their audio.
+ *
+ * The MediaProjection APIs are not invoked for casting *only audio* to another device because
+ * MediaProjection is only concerned with *screen* sharing (see b/342169876). We listen to
+ * MediaRouter APIs here to cover audio-only casting.
+ *
+ * Note that this means we will start showing the cast chip before the casting actually starts,
+ * for **both** audio-only casting and screen casting. MediaRouter is aware of all
+ * cast-to-other-device events, and MediaRouter immediately marks a device as "connecting" once
+ * a user selects what device they'd like to cast to, even if they haven't hit "Start casting"
+ * yet. All of SysUI considers "connecting" devices to be casting (see
+ * [com.android.systemui.statusbar.policy.CastDevice.isCasting]), so the chip will follow the
+ * same convention and start showing once a device is selected. See b/269975671.
+ */
+ private val routerChip =
+ mediaRouterChipInteractor.mediaRouterCastingState
+ .map { routerModel ->
+ when (routerModel) {
+ is MediaRouterCastModel.DoingNothing -> OngoingActivityChipModel.Hidden
+ is MediaRouterCastModel.Casting -> {
+ // A consequence of b/269975671 is that MediaRouter will mark a device as
+ // casting before casting has actually started. To alleviate this bug a bit,
+ // we won't show a timer for MediaRouter events. That way, we won't show a
+ // timer if cast hasn't actually started.
+ //
+ // This does mean that the audio-only casting chip will *never* show a
+ // timer, because audio-only casting never activates the MediaProjection
+ // APIs and those are the only cast APIs that show a timer.
+ createIconOnlyCastChip()
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+
+ override val chip: StateFlow<OngoingActivityChipModel> =
+ combine(projectionChip, routerChip) { projection, router ->
+ // A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
+ // different times when *screen* casting:
+ //
+ // 1. When the user chooses what device to cast to, the MediaRouter APIs mark the
+ // device as casting (even though casting hasn't actually started yet). At this
+ // point, `routerChip` is [OngoingActivityChipModel.Shown] but `projectionChip` is
+ // [OngoingActivityChipModel.Hidden], and we'll show the router chip.
+ //
+ // 2. Once casting has actually started, the MediaProjection APIs become aware of
+ // the device. At this point, both `routerChip` and `projectionChip` are
+ // [OngoingActivityChipModel.Shown].
+ //
+ // Because the MediaProjection APIs have activated, we know that the user is screen
+ // casting (not audio casting). We need to switch to using `projectionChip` because
+ // that chip will show information specific to screen casting. The `projectionChip`
+ // will also show a timer, as opposed to `routerChip`'s icon-only display.
+ if (projection is OngoingActivityChipModel.Shown) {
+ projection
+ } else {
+ router
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden)
+
/** Stops the currently active projection. */
private fun stopProjecting() {
mediaProjectionChipInteractor.stopProjecting()
}
- private fun createCastToOtherDeviceChip(
+ private fun stopMediaRouterCasting() {
+ mediaRouterChipInteractor.stopCasting()
+ }
+
+ private fun createCastScreenToOtherDeviceChip(
state: ProjectionChipModel.Projecting,
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
Icon.Resource(
CAST_TO_OTHER_DEVICE_ICON,
- // Note: This string is "Casting screen", which is okay right now because this
- // chip does not currently support audio-only casting. If the chip starts
- // supporting audio-only casting (see b/342169876), update the content
- // description to just "Casting".
+ // This string is "Casting screen"
ContentDescription.Resource(
- R.string.cast_to_other_device_chip_accessibility_label,
+ R.string.cast_screen_to_other_device_chip_accessibility_label,
),
),
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
- createCastToOtherDeviceDialogDelegate(state),
+ createCastScreenToOtherDeviceDialogDelegate(state),
dialogTransitionAnimator,
),
)
}
- private fun createCastToOtherDeviceDialogDelegate(state: ProjectionChipModel.Projecting) =
- EndCastToOtherDeviceDialogDelegate(
+ private fun createIconOnlyCastChip(): OngoingActivityChipModel.Shown {
+ return OngoingActivityChipModel.Shown.IconOnly(
+ icon =
+ Icon.Resource(
+ CAST_TO_OTHER_DEVICE_ICON,
+ // This string is just "Casting"
+ ContentDescription.Resource(R.string.accessibility_casting),
+ ),
+ colors = ColorsModel.Red,
+ createDialogLaunchOnClickListener(
+ createGenericCastToOtherDeviceDialogDelegate(),
+ dialogTransitionAnimator,
+ ),
+ )
+ }
+
+ private fun createCastScreenToOtherDeviceDialogDelegate(
+ state: ProjectionChipModel.Projecting,
+ ) =
+ EndCastScreenToOtherDeviceDialogDelegate(
endMediaProjectionDialogHelper,
stopAction = this::stopProjecting,
state,
)
+ private fun createGenericCastToOtherDeviceDialogDelegate() =
+ EndGenericCastToOtherDeviceDialogDelegate(
+ endMediaProjectionDialogHelper,
+ stopAction = this::stopMediaRouterCasting,
+ )
+
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 5a1146d..1434dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.NotificationManager
@@ -27,9 +25,10 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -58,7 +57,6 @@
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
@@ -89,7 +87,7 @@
private val headsUpManager: HeadsUpManager,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
private val keyguardRepository: KeyguardRepository,
- private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardCoordinatorLogger,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
@@ -126,8 +124,9 @@
private suspend fun trackSeenNotifications() {
// Whether or not keyguard is visible (or occluded).
val isKeyguardPresent: Flow<Boolean> =
- keyguardTransitionRepository.transitions
- .map { step -> step.to != KeyguardState.GONE }
+ keyguardTransitionInteractor
+ .transitionValue(Scenes.Gone, stateWithoutSceneContainer = KeyguardState.GONE)
+ .map { it == 0f }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 534d9d2..900201f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -186,6 +186,7 @@
interactor.configurationBasedDimensions
.map {
when {
+ !it.useSplitShade -> 0
it.useLargeScreenHeader -> it.marginTopLargeScreen
else -> it.marginTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 6012ecd..775f34d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -250,6 +250,7 @@
mLevel = (int) (100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+ int previousPluggedChargingSource = mPluggedChargingSource;
mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
mPluggedIn = mPluggedChargingSource != 0;
final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
@@ -276,7 +277,9 @@
mIsBatteryDefender = isBatteryDefender;
fireIsBatteryDefenderChanged();
}
-
+ if (mPluggedChargingSource != previousPluggedChargingSource) {
+ updatePowerSave();
+ }
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 9c8a481..501fee6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -20,15 +20,15 @@
import com.android.systemui.util.RingerModeTrackerImpl;
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository;
import com.android.systemui.util.animation.data.repository.AnimationStatusRepositoryImpl;
+import com.android.systemui.util.icons.AppCategoryIconProvider;
+import com.android.systemui.util.icons.AppCategoryIconProviderImpl;
import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds;
import dagger.Module;
/** Dagger Module for code in the util package. */
-@Module(includes = {
- UtilWrapperModule.class
- })
+@Module(includes = {UtilWrapperModule.class})
public interface UtilModule {
/** */
@Binds
@@ -37,4 +37,8 @@
@Binds
AnimationStatusRepository provideAnimationStatus(
AnimationStatusRepositoryImpl ringerModeTrackerImpl);
+
+ /** */
+ @Binds
+ AppCategoryIconProvider appCategoryIconProvider(AppCategoryIconProviderImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt b/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt
new file mode 100644
index 0000000..6e3f8f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.content.Intent
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.os.RemoteException
+import android.util.Log
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.PackageManagerWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface AppCategoryIconProvider {
+ /** Returns the [Icon] of the default assistant app, if it exists. */
+ suspend fun assistantAppIcon(): Icon?
+
+ /**
+ * Returns the [Icon] of the default app of [category], if it exists. Category can be for
+ * example [Intent.CATEGORY_APP_EMAIL] or [Intent.CATEGORY_APP_CALCULATOR].
+ */
+ suspend fun categoryAppIcon(category: String): Icon?
+}
+
+class AppCategoryIconProviderImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val assistManager: AssistManager,
+ private val packageManager: PackageManager,
+ private val packageManagerWrapper: PackageManagerWrapper,
+) : AppCategoryIconProvider {
+
+ override suspend fun assistantAppIcon(): Icon? {
+ val assistInfo = assistManager.assistInfo ?: return null
+ return getPackageIcon(assistInfo.packageName)
+ }
+
+ override suspend fun categoryAppIcon(category: String): Icon? {
+ val intent = Intent(Intent.ACTION_MAIN).apply { addCategory(category) }
+ val packageInfo = getPackageInfo(intent) ?: return null
+ return getPackageIcon(packageInfo.packageName)
+ }
+
+ private suspend fun getPackageInfo(intent: Intent): PackageInfo? =
+ withContext(backgroundDispatcher) {
+ val packageName =
+ packageManagerWrapper
+ .resolveActivity(/* intent= */ intent, /* flags= */ 0)
+ ?.activityInfo
+ ?.packageName ?: return@withContext null
+ return@withContext getPackageInfo(packageName)
+ }
+
+ private suspend fun getPackageIcon(packageName: String): Icon? {
+ val appInfo = getPackageInfo(packageName)?.applicationInfo ?: return null
+ return if (appInfo.icon != 0) {
+ Icon.createWithResource(appInfo.packageName, appInfo.icon)
+ } else {
+ null
+ }
+ }
+
+ private suspend fun getPackageInfo(packageName: String): PackageInfo? =
+ withContext(backgroundDispatcher) {
+ try {
+ return@withContext packageManager.getPackageInfo(packageName, /* flags= */ 0)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to retrieve package info for $packageName")
+ return@withContext null
+ }
+ }
+
+ companion object {
+ private const val TAG = "DefaultAppsIconProvider"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 160ae86..fe54044 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -19,6 +19,7 @@
import android.database.ContentObserver
import android.net.Uri
import android.provider.Settings.SettingNotFoundException
+import androidx.annotation.WorkerThread
import com.android.app.tracing.TraceUtils.trace
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -59,10 +60,13 @@
fun getUriFor(name: String): Uri
/**
- * Convenience wrapper around [ContentResolver.registerContentObserver].'
- *
+ * Registers listener for a given content observer <b>while blocking the current thread</b>.
* Implicitly calls [getUriFor] on the passed in name.
+ *
+ * This should not be called from the main thread, use [registerContentObserver] or
+ * [registerContentObserverAsync] instead.
*/
+ @WorkerThread
fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -90,7 +94,13 @@
registerContentObserverSync(getUriFor(name), settingsObserver)
}
- /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ /**
+ * Registers listener for a given content observer <b>while blocking the current thread</b>.
+ *
+ * This should not be called from the main thread, use [registerContentObserver] or
+ * [registerContentObserverAsync] instead.
+ */
+ @WorkerThread
fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) =
registerContentObserverSync(uri, false, settingsObserver)
@@ -157,7 +167,13 @@
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
}
- /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ /**
+ * Registers listener for a given content observer <b>while blocking the current thread</b>.
+ *
+ * This should not be called from the main thread, use [registerContentObserver] or
+ * [registerContentObserverAsync] instead.
+ */
+ @WorkerThread
fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -200,7 +216,13 @@
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
}
- /** See [ContentResolver.unregisterContentObserver]. */
+ /**
+ * Unregisters the given content observer <b>while blocking the current thread</b>.
+ *
+ * This should not be called from the main thread, use [unregisterContentObserver] or
+ * [unregisterContentObserverAsync] instead.
+ */
+ @WorkerThread
fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
trace({ "SP#unregisterObserver" }) {
getContentResolver().unregisterContentObserver(settingsObserver)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt
new file mode 100644
index 0000000..6859191
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.volume
+
+import android.media.IVolumeController
+import com.android.settingslib.media.data.repository.VolumeControllerEvent
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * This class is a bridge between
+ * [com.android.settingslib.volume.data.repository.AudioRepository.volumeControllerEvents] and the
+ * old code that uses [IVolumeController] interface directly.
+ */
+class VolumeControllerCollector
+@Inject
+constructor(@Application private val coroutineScope: CoroutineScope) {
+
+ /** Collects [Flow] of [VolumeControllerEvent] into [IVolumeController]. */
+ fun collectToController(
+ eventsFlow: Flow<VolumeControllerEvent>,
+ controller: IVolumeController
+ ) =
+ coroutineScope.launch {
+ eventsFlow.collect { event ->
+ when (event) {
+ is VolumeControllerEvent.VolumeChanged ->
+ controller.volumeChanged(event.streamType, event.flags)
+ VolumeControllerEvent.Dismiss -> controller.dismiss()
+ is VolumeControllerEvent.DisplayCsdWarning ->
+ controller.displayCsdWarning(event.csdWarning, event.displayDurationMs)
+ is VolumeControllerEvent.DisplaySafeVolumeWarning ->
+ controller.displaySafeVolumeWarning(event.flags)
+ is VolumeControllerEvent.MasterMuteChanged ->
+ controller.masterMuteChanged(event.flags)
+ is VolumeControllerEvent.SetA11yMode -> controller.setA11yMode(event.mode)
+ is VolumeControllerEvent.SetLayoutDirection ->
+ controller.setLayoutDirection(event.layoutDirection)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 97f5efc..677d1fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1799,8 +1799,10 @@
when {
fingerprint != null && face != null -> "coex"
fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
- fingerprint != null && !fingerprint.isAnySidefpsType ->
- "fingerprint only, non-sideFps"
+ fingerprint != null && fingerprint.isAnyUdfpsType -> "fingerprint only, udfps"
+ fingerprint != null &&
+ fingerprint.sensorType == FingerprintSensorProperties.TYPE_REAR ->
+ "fingerprint only, rearFps"
face != null -> "face only"
else -> "?"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
new file mode 100644
index 0000000..84ec1a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 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.mediarouter.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.CastDevice
+import com.android.systemui.statusbar.policy.fakeCastController
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class MediaRouterRepositoryTest : SysuiTestCase() {
+ val kosmos = Kosmos()
+ val testScope = kosmos.testScope
+ val castController = kosmos.fakeCastController
+
+ val underTest = kosmos.realMediaRouterRepository
+
+ @Test
+ fun castDevices_empty_isEmpty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.castDevices)
+ // Required to let the listener attach before the devices get set
+ runCurrent()
+
+ castController.castDevices = emptyList()
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun castDevices_onlyIncludesMediaRouterOriginDevices() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.castDevices)
+ // Required to let the listener attach before the devices get set
+ runCurrent()
+
+ val projectionDevice =
+ CastDevice(
+ id = "idProjection",
+ name = "name",
+ description = "desc",
+ state = CastDevice.CastState.Connected,
+ origin = CastDevice.CastOrigin.MediaProjection,
+ )
+ val routerDevice1 =
+ CastDevice(
+ id = "idRouter1",
+ name = "name",
+ description = "desc",
+ state = CastDevice.CastState.Connected,
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+
+ val routerDevice2 =
+ CastDevice(
+ id = "idRouter2",
+ name = "name",
+ description = "desc",
+ state = CastDevice.CastState.Connected,
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ castController.setCastDevices(listOf(projectionDevice, routerDevice1, routerDevice2))
+
+ assertThat(latest).containsExactly(routerDevice1, routerDevice2).inOrder()
+ }
+
+ @Test
+ fun stopCasting_notifiesCastController() {
+ val device =
+ CastDevice(
+ id = "id",
+ name = "name",
+ description = "desc",
+ state = CastDevice.CastState.Connected,
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+
+ underTest.stopCasting(device)
+
+ assertThat(castController.lastStoppedDevice).isEqualTo(device)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
new file mode 100644
index 0000000..8a6a50d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 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.statusbar.chips.casttootherdevice.domian.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
+import com.android.systemui.statusbar.policy.CastDevice
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class MediaRouterChipInteractorTest : SysuiTestCase() {
+ val kosmos = Kosmos()
+ val testScope = kosmos.testScope
+ val mediaRouterRepository = kosmos.fakeMediaRouterRepository
+
+ val underTest = kosmos.mediaRouterChipInteractor
+
+ @Test
+ fun mediaRouterCastingState_noDevices_doingNothing() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value = emptyList()
+
+ assertThat(latest).isEqualTo(MediaRouterCastModel.DoingNothing)
+ }
+
+ @Test
+ fun mediaRouterCastingState_disconnectedDevice_doingNothing() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Disconnected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat(latest).isEqualTo(MediaRouterCastModel.DoingNothing)
+ }
+
+ @Test
+ fun mediaRouterCastingState_connectingDevice_casting() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connecting,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ }
+
+ @Test
+ fun mediaRouterCastingState_connectedDevice_casting() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat(latest).isEqualTo(MediaRouterCastModel.Casting)
+ }
+
+ @Test
+ fun stopCasting_noDevices_doesNothing() =
+ testScope.runTest {
+ collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value = emptyList()
+ // Let the interactor catch up to the repo value
+ runCurrent()
+
+ underTest.stopCasting()
+
+ assertThat(mediaRouterRepository.lastStoppedDevice).isNull()
+ }
+
+ @Test
+ fun stopCasting_disconnectedDevice_doesNothing() =
+ testScope.runTest {
+ collectLastValue(underTest.mediaRouterCastingState)
+
+ mediaRouterRepository.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Disconnected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+ // Let the interactor catch up to the repo value
+ runCurrent()
+
+ underTest.stopCasting()
+
+ assertThat(mediaRouterRepository.lastStoppedDevice).isNull()
+ }
+
+ @Test
+ fun stopCasting_connectingDevice_notifiesRepo() =
+ testScope.runTest {
+ collectLastValue(underTest.mediaRouterCastingState)
+
+ val device =
+ CastDevice(
+ state = CastDevice.CastState.Connecting,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ mediaRouterRepository.castDevices.value = listOf(device)
+ // Let the interactor catch up to the repo value
+ runCurrent()
+
+ underTest.stopCasting()
+
+ assertThat(mediaRouterRepository.lastStoppedDevice).isEqualTo(device)
+ }
+
+ @Test
+ fun stopCasting_connectedDevice_notifiesRepo() =
+ testScope.runTest {
+ collectLastValue(underTest.mediaRouterCastingState)
+
+ val device =
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ mediaRouterRepository.castDevices.value = listOf(device)
+ // Let the interactor catch up to the repo value
+ runCurrent()
+
+ underTest.stopCasting()
+
+ assertThat(mediaRouterRepository.lastStoppedDevice).isEqualTo(device)
+ }
+
+ @Test
+ fun stopCasting_multipleConnectedDevices_notifiesRepoOfFirst() =
+ testScope.runTest {
+ collectLastValue(underTest.mediaRouterCastingState)
+
+ val device1 =
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id1",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ val device2 =
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id2",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ mediaRouterRepository.castDevices.value = listOf(device1, device2)
+ // Let the interactor catch up to the repo value
+ runCurrent()
+
+ underTest.stopCasting()
+
+ assertThat(mediaRouterRepository.lastStoppedDevice).isEqualTo(device1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index c6fb481..e9d6f0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -48,10 +48,10 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class EndCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
+class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() {
private val kosmos = Kosmos().also { it.testCase = this }
private val sysuiDialog = mock<SystemUIDialog>()
- private lateinit var underTest: EndCastToOtherDeviceDialogDelegate
+ private lateinit var underTest: EndCastScreenToOtherDeviceDialogDelegate
@Test
fun icon() {
@@ -68,7 +68,7 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
+ verify(sysuiDialog).setTitle(R.string.cast_screen_to_other_device_stop_dialog_title)
}
@Test
@@ -78,7 +78,7 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
verify(sysuiDialog)
- .setMessage(context.getString(R.string.cast_to_other_device_stop_dialog_message))
+ .setMessage(context.getString(R.string.cast_screen_to_other_device_stop_dialog_message))
}
@Test
@@ -99,7 +99,7 @@
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
- // It'd be nice to use R.string.cast_to_other_device_stop_dialog_message_specific_app
+ // It'd be nice to use R.string.cast_screen_to_other_device_stop_dialog_message_specific_app
// directly, but it includes the <b> tags which aren't in the returned string.
val result = argumentCaptor<CharSequence>()
verify(sysuiDialog).setMessage(result.capture())
@@ -142,7 +142,7 @@
private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
underTest =
- EndCastToOtherDeviceDialogDelegate(
+ EndCastScreenToOtherDeviceDialogDelegate(
kosmos.endMediaProjectionDialogHelper,
stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
ProjectionChipModel.Projecting(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
new file mode 100644
index 0000000..0af423d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 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.statusbar.chips.casttootherdevice.ui.view
+
+import android.content.DialogInterface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.CastDevice
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
+ private val kosmos = Kosmos().also { it.testCase = this }
+ private val sysuiDialog = mock<SystemUIDialog>()
+ private lateinit var underTest: EndGenericCastToOtherDeviceDialogDelegate
+
+ @Test
+ fun icon() {
+ createAndSetDelegate()
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog).setIcon(R.drawable.ic_cast_connected)
+ }
+
+ @Test
+ fun title() {
+ createAndSetDelegate()
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog).setTitle(R.string.cast_to_other_device_stop_dialog_title)
+ }
+
+ @Test
+ fun message() {
+ createAndSetDelegate()
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog).setMessage(R.string.cast_to_other_device_stop_dialog_message)
+ }
+
+ @Test
+ fun negativeButton() {
+ createAndSetDelegate()
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(sysuiDialog).setNegativeButton(R.string.close_dialog_button, null)
+ }
+
+ @Test
+ fun positiveButton() =
+ kosmos.testScope.runTest {
+ createAndSetDelegate()
+
+ // Set up a real device so the stop works correctly
+ collectLastValue(kosmos.mediaRouterChipInteractor.mediaRouterCastingState)
+ val device =
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ kosmos.fakeMediaRouterRepository.castDevices.value = listOf(device)
+ // Let everything catch up to the repo value
+ runCurrent()
+ runCurrent()
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ val clickListener = argumentCaptor<DialogInterface.OnClickListener>()
+
+ // Verify the button has the right text
+ verify(sysuiDialog)
+ .setPositiveButton(
+ eq(R.string.cast_to_other_device_stop_dialog_button),
+ clickListener.capture()
+ )
+
+ // Verify that clicking the button stops the recording
+ assertThat(kosmos.fakeMediaRouterRepository.lastStoppedDevice).isNull()
+
+ clickListener.firstValue.onClick(mock<DialogInterface>(), 0)
+ runCurrent()
+
+ assertThat(kosmos.fakeMediaRouterRepository.lastStoppedDevice).isEqualTo(device)
+ }
+
+ private fun createAndSetDelegate() {
+ underTest =
+ EndGenericCastToOtherDeviceDialogDelegate(
+ kosmos.endMediaProjectionDialogHelper,
+ stopAction = kosmos.mediaRouterChipInteractor::stopCasting,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index 74b6ae2..fe29140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.mockDialogTransitionAnimator
+import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -28,8 +29,10 @@
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
import com.android.systemui.res.R
-import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
+import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndGenericCastToOtherDeviceDialogDelegate
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
@@ -38,6 +41,7 @@
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -55,9 +59,11 @@
private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
+ private val mediaRouterRepo = kosmos.fakeMediaRouterRepository
private val systemClock = kosmos.fakeSystemClock
- private val mockCastDialog = mock<SystemUIDialog>()
+ private val mockScreenCastDialog = mock<SystemUIDialog>()
+ private val mockGenericCastDialog = mock<SystemUIDialog>()
private val chipBackgroundView = mock<ChipBackgroundContainer>()
private val chipView =
@@ -76,14 +82,25 @@
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
- whenever(kosmos.mockSystemUIDialogFactory.create(any<EndCastToOtherDeviceDialogDelegate>()))
- .thenReturn(mockCastDialog)
+ whenever(
+ kosmos.mockSystemUIDialogFactory.create(
+ any<EndCastScreenToOtherDeviceDialogDelegate>()
+ )
+ )
+ .thenReturn(mockScreenCastDialog)
+ whenever(
+ kosmos.mockSystemUIDialogFactory.create(
+ any<EndGenericCastToOtherDeviceDialogDelegate>()
+ )
+ )
+ .thenReturn(mockGenericCastDialog)
}
@Test
fun chip_notProjectingState_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
+ mediaRouterRepo.castDevices.value = emptyList()
mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
@@ -91,9 +108,10 @@
}
@Test
- fun chip_singleTaskState_otherDevicesPackage_isShownAsTimer() =
+ fun chip_projectionIsSingleTaskState_otherDevicesPackage_isShownAsTimer_forScreen() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
+ mediaRouterRepo.castDevices.value = emptyList()
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.SingleTask(
@@ -104,13 +122,15 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
- assertThat(icon.contentDescription).isNotNull()
+ assertThat((icon.contentDescription as ContentDescription.Resource).res)
+ .isEqualTo(R.string.cast_screen_to_other_device_chip_accessibility_label)
}
@Test
- fun chip_entireScreenState_otherDevicesPackage_isShownAsTimer() =
+ fun chip_projectionIsEntireScreenState_otherDevicesPackage_isShownAsTimer_forScreen() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
+ mediaRouterRepo.castDevices.value = emptyList()
mediaProjectionRepo.mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
@@ -118,7 +138,72 @@
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
val icon = (latest as OngoingActivityChipModel.Shown).icon
assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
- assertThat(icon.contentDescription).isNotNull()
+ assertThat((icon.contentDescription as ContentDescription.Resource).res)
+ .isEqualTo(R.string.cast_screen_to_other_device_chip_accessibility_label)
+ }
+
+ @Test
+ fun chip_routerStateDoingNothing_isHidden() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ mediaRouterRepo.castDevices.value = emptyList()
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ fun chip_routerStateCasting_isShownAsGenericIconOnly() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ val icon = (latest as OngoingActivityChipModel.Shown).icon
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ // This content description is just generic "Casting", not "Casting screen"
+ assertThat((icon.contentDescription as ContentDescription.Resource).res)
+ .isEqualTo(R.string.accessibility_casting)
+ }
+
+ @Test
+ fun chip_projectingAndRouterCasting_projectionInfoShown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ // Only the projection info will show a timer
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+ val icon = (latest as OngoingActivityChipModel.Shown).icon
+ assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ // MediaProjection == screen casting, so this content description reflects that we're
+ // using the MediaProjection information.
+ assertThat((icon.contentDescription as ContentDescription.Resource).res)
+ .isEqualTo(R.string.cast_screen_to_other_device_chip_accessibility_label)
}
@Test
@@ -133,7 +218,7 @@
}
@Test
- fun chip_singleTaskState_normalPackage_isHidden() =
+ fun chip_projectionIsSingleTaskState_normalPackage_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -144,7 +229,7 @@
}
@Test
- fun chip_entireScreenState_normalPackage_isHidden() =
+ fun chip_projectionIsEntireScreenState_normalPackage_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -155,7 +240,7 @@
}
@Test
- fun chip_timeResetsOnEachNewShare() =
+ fun chip_projectionOnly_timeResetsOnEachNewShare() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -181,7 +266,38 @@
}
@Test
- fun chip_entireScreen_clickListenerShowsCastDialog() =
+ fun chip_routerInfoThenProjectionInfo_switchesToTimer() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ // First, set only MediaRouter to have information and verify we just show the icon
+ systemClock.setElapsedRealtime(1234)
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+
+ // Later, set MediaProjection to also have information
+ systemClock.setElapsedRealtime(5678)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
+
+ // Verify the new time is used
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
+ }
+
+ @Test
+ fun chip_projectionStateEntireScreen_clickListenerShowsScreenCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
mediaProjectionRepo.mediaProjectionState.value =
@@ -193,7 +309,7 @@
clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
- eq(mockCastDialog),
+ eq(mockScreenCastDialog),
eq(chipBackgroundView),
eq(null),
ArgumentMatchers.anyBoolean(),
@@ -201,7 +317,7 @@
}
@Test
- fun chip_singleTask_clickListenerShowsCastDialog() =
+ fun chip_projectionStateSingleTask_clickListenerShowsScreenCastDialog() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -217,7 +333,36 @@
clickListener!!.onClick(chipView)
verify(kosmos.mockDialogTransitionAnimator)
.showFromView(
- eq(mockCastDialog),
+ eq(mockScreenCastDialog),
+ eq(chipBackgroundView),
+ eq(null),
+ ArgumentMatchers.anyBoolean(),
+ )
+ }
+
+ @Test
+ fun chip_routerStateCasting_clickListenerShowsGenericCastDialog() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ mediaRouterRepo.castDevices.value =
+ listOf(
+ CastDevice(
+ state = CastDevice.CastState.Connected,
+ id = "id",
+ name = "name",
+ description = "desc",
+ origin = CastDevice.CastOrigin.MediaRouter,
+ )
+ )
+
+ val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
+ assertThat(clickListener).isNotNull()
+
+ clickListener!!.onClick(chipView)
+ verify(kosmos.mockDialogTransitionAnimator)
+ .showFromView(
+ eq(mockGenericCastDialog),
eq(chipBackgroundView),
eq(null),
ArgumentMatchers.anyBoolean(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index ee929ae..f9ad5ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
@@ -61,7 +61,7 @@
underTest.getDialogMessage(
MediaProjectionState.Projecting.EntireScreen("host.package"),
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
@@ -84,7 +84,7 @@
underTest.getDialogMessage(
projectionState,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
@@ -109,7 +109,7 @@
underTest.getDialogMessage(
projectionState,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
// It'd be nice to use the R.string resources directly, but they include the <b> tags which
@@ -123,7 +123,7 @@
underTest.getDialogMessage(
specificTaskInfo = null,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app,
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
@@ -141,7 +141,7 @@
underTest.getDialogMessage(
task,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result).isEqualTo(context.getString(R.string.accessibility_home))
@@ -161,7 +161,7 @@
underTest.getDialogMessage(
task,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result.toString()).isEqualTo("You will stop casting Fake Package")
@@ -186,7 +186,7 @@
underTest.getDialogMessage(
projectionState,
R.string.accessibility_home,
- R.string.cast_to_other_device_stop_dialog_message_specific_app
+ R.string.cast_screen_to_other_device_stop_dialog_message_specific_app,
)
assertThat(result.toString()).isEqualTo("You will stop casting Fake & Package <Here>")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 25533d8..d87b3e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -19,16 +19,23 @@
import android.app.Notification
import android.os.UserHandle
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setTransition
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -49,6 +56,8 @@
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,22 +71,28 @@
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardCoordinatorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
private val headsUpManager: HeadsUpManager = mock()
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Test
fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
@@ -147,10 +162,9 @@
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(false)
runKeyguardCoordinatorTest {
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
)
// WHEN: A notification is posted
@@ -163,24 +177,20 @@
// WHEN: The keyguard is now showing
keyguardRepository.setKeyguardShowing(true)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
)
- testScheduler.runCurrent()
// THEN: The notification is recognized as "seen" and is filtered out.
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
// WHEN: The keyguard goes away
keyguardRepository.setKeyguardShowing(false)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.GONE,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE)
)
- testScheduler.runCurrent()
// THEN: The notification is shown regardless
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
@@ -344,9 +354,9 @@
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -356,21 +366,17 @@
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
)
- testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
)
- testScheduler.runCurrent()
// THEN: The notification is now recognized as "seen" and is filtered out.
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
@@ -383,9 +389,9 @@
keyguardRepository.setKeyguardShowing(true)
runKeyguardCoordinatorTest {
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -393,9 +399,9 @@
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ this.testScheduler,
)
// WHEN: Keyguard is shown again
@@ -413,10 +419,9 @@
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setIsDozing(false)
runKeyguardCoordinatorTest {
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
)
val firstEntry = NotificationEntryBuilder().setId(1).build()
collectionListener.onEntryAdded(firstEntry)
@@ -437,21 +442,17 @@
// WHEN: the keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
)
- testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
)
- testScheduler.runCurrent()
// THEN: The first notification is considered seen and is filtered out.
assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
@@ -468,9 +469,9 @@
keyguardRepository.setIsDozing(false)
runKeyguardCoordinatorTest {
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -498,18 +499,18 @@
// WHEN: the keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ this.testScheduler,
)
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -525,9 +526,9 @@
keyguardRepository.setIsDozing(false)
runKeyguardCoordinatorTest {
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -555,18 +556,18 @@
// WHEN: the keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ this.testScheduler,
)
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -582,9 +583,9 @@
keyguardRepository.setIsDozing(false)
runKeyguardCoordinatorTest {
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -608,18 +609,18 @@
// WHEN: the keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- this.testScheduler,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ this.testScheduler,
)
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.GONE,
- to = KeyguardState.LOCKSCREEN,
- this.testScheduler,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
)
testScheduler.runCurrent()
@@ -646,7 +647,7 @@
headsUpManager,
keyguardNotifVisibilityProvider,
keyguardRepository,
- keyguardTransitionRepository,
+ kosmos.keyguardTransitionInteractor,
KeyguardCoordinatorLogger(logcatLogBuffer()),
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
@@ -706,4 +707,12 @@
)
}
}
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index ed8843b..db829a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -23,6 +23,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -308,6 +310,52 @@
mBatteryController.fireBatteryLevelChanged();
}
+ @Test
+ public void plugAndUnplugWhenInBatterySaver_stateUpdatedWithoutBatterySaverBroadcast() {
+ PowerSaveState state = new PowerSaveState.Builder()
+ .setBatterySaverEnabled(false)
+ .build();
+ when(mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD)).thenReturn(state);
+
+ // Set up on power save and not charging
+ when(mPowerManager.isPowerSaveMode()).thenReturn(true);
+ mBatteryController.onReceive(
+ getContext(), new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ mBatteryController.onReceive(getContext(), createChargingIntent(false));
+
+ TestCallback callback = new TestCallback();
+ mBatteryController.addCallback(callback);
+
+ assertThat(callback.pluggedIn).isFalse();
+ assertThat(callback.powerSaverOn).isTrue();
+
+ // Plug in (battery saver turns off)
+ when(mPowerManager.isPowerSaveMode()).thenReturn(false);
+ mBatteryController.onReceive(getContext(), createChargingIntent(true));
+
+ assertThat(callback.pluggedIn).isTrue();
+ assertThat(callback.powerSaverOn).isFalse();
+
+ // Unplug (battery saver turns back on)
+ when(mPowerManager.isPowerSaveMode()).thenReturn(true);
+ mBatteryController.onReceive(getContext(), createChargingIntent(false));
+
+ assertThat(callback.pluggedIn).isFalse();
+ assertThat(callback.powerSaverOn).isTrue();
+ }
+
+ private Intent createChargingIntent(boolean charging) {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ if (charging) {
+ return intent
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
+ .putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_AC);
+ } else {
+ return intent
+ .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
+ }
+ }
+
private void setupIncompatibleCharging() {
final List<UsbPort> usbPorts = new ArrayList<>();
usbPorts.add(mUsbPort);
@@ -318,4 +366,19 @@
when(mUsbPortStatus.getComplianceWarnings())
.thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
}
+
+ private static class TestCallback
+ implements BatteryController.BatteryStateChangeCallback {
+ boolean pluggedIn = false;
+ boolean powerSaverOn = false;
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ this.pluggedIn = pluggedIn;
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ this.powerSaverOn = isPowerSave;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
new file mode 100644
index 0000000..ef41b6ee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.CATEGORY_APP_BROWSER
+import android.content.Intent.CATEGORY_APP_CONTACTS
+import android.content.Intent.CATEGORY_APP_EMAIL
+import android.content.mockPackageManager
+import android.content.mockPackageManagerWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.ResolveInfo
+import android.graphics.drawable.Icon
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.mockAssistManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppCategoryIconProviderTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val packageManagerWrapper = kosmos.mockPackageManagerWrapper
+ private val packageManager = kosmos.mockPackageManager
+ private val assistManager = kosmos.mockAssistManager
+ private val provider = kosmos.appCategoryIconProvider
+
+ @Before
+ fun setUp() {
+ whenever(packageManagerWrapper.resolveActivity(any<Intent>(), any<Int>())).thenAnswer {
+ invocation ->
+ val category = (invocation.arguments[0] as Intent).categories.first()
+ val categoryAppIcon =
+ categoryAppIcons.firstOrNull { it.category == category } ?: return@thenAnswer null
+ val activityInfo = ActivityInfo().also { it.packageName = categoryAppIcon.packageName }
+ return@thenAnswer ResolveInfo().also { it.activityInfo = activityInfo }
+ }
+ whenever(packageManager.getPackageInfo(any<String>(), any<Int>())).thenAnswer { invocation
+ ->
+ val packageName = invocation.arguments[0] as String
+ val categoryAppIcon =
+ categoryAppIcons.firstOrNull { it.packageName == packageName }
+ ?: return@thenAnswer null
+ val applicationInfo =
+ ApplicationInfo().also {
+ it.packageName = packageName
+ it.icon = categoryAppIcon.iconResId
+ }
+ return@thenAnswer PackageInfo().also {
+ it.packageName = packageName
+ it.applicationInfo = applicationInfo
+ }
+ }
+ }
+
+ @Test
+ fun assistantAppIcon_defaultAssistantSet_returnsIcon() =
+ testScope.runTest {
+ whenever(assistManager.assistInfo)
+ .thenReturn(ComponentName(ASSISTANT_PACKAGE, ASSISTANT_CLASS))
+
+ val icon = provider.assistantAppIcon() as Icon
+
+ assertThat(icon.resPackage).isEqualTo(ASSISTANT_PACKAGE)
+ assertThat(icon.resId).isEqualTo(ASSISTANT_ICON_RES_ID)
+ }
+
+ @Test
+ fun assistantAppIcon_defaultAssistantNotSet_returnsNull() =
+ testScope.runTest {
+ whenever(assistManager.assistInfo).thenReturn(null)
+
+ assertThat(provider.assistantAppIcon()).isNull()
+ }
+
+ @Test
+ fun categoryAppIcon_returnsIconOfKnownBrowserApp() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_BROWSER) as Icon
+
+ assertThat(icon.resPackage).isEqualTo(BROWSER_PACKAGE)
+ assertThat(icon.resId).isEqualTo(BROWSER_ICON_RES_ID)
+ }
+ }
+
+ @Test
+ fun categoryAppIcon_returnsIconOfKnownContactsApp() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_CONTACTS) as Icon
+
+ assertThat(icon.resPackage).isEqualTo(CONTACTS_PACKAGE)
+ assertThat(icon.resId).isEqualTo(CONTACTS_ICON_RES_ID)
+ }
+ }
+
+ @Test
+ fun categoryAppIcon_noDefaultAppForCategoryEmail_returnsNull() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_EMAIL)
+
+ assertThat(icon).isNull()
+ }
+ }
+
+ private companion object {
+ private const val ASSISTANT_PACKAGE = "the.assistant.app"
+ private const val ASSISTANT_CLASS = "the.assistant.app.class"
+ private const val ASSISTANT_ICON_RES_ID = 123
+
+ private const val BROWSER_PACKAGE = "com.test.browser"
+ private const val BROWSER_ICON_RES_ID = 1
+
+ private const val CONTACTS_PACKAGE = "app.test.contacts"
+ private const val CONTACTS_ICON_RES_ID = 234
+
+ private val categoryAppIcons =
+ listOf(
+ App(ROLE_ASSISTANT, ASSISTANT_PACKAGE, ASSISTANT_ICON_RES_ID),
+ App(CATEGORY_APP_BROWSER, BROWSER_PACKAGE, BROWSER_ICON_RES_ID),
+ App(CATEGORY_APP_CONTACTS, CONTACTS_PACKAGE, CONTACTS_ICON_RES_ID),
+ )
+ }
+
+ private class App(val category: String, val packageName: String, val iconResId: Int)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt
new file mode 100644
index 0000000..dd78e4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 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.volume
+
+import android.media.IVolumeController
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.media.data.repository.VolumeControllerEvent
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class VolumeControllerCollectorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val eventsFlow = MutableStateFlow<VolumeControllerEvent?>(null)
+ private val underTest = VolumeControllerCollector(kosmos.applicationCoroutineScope)
+
+ private val volumeController = mock<IVolumeController> {}
+
+ @Test
+ fun volumeControllerEvent_volumeChanged_callsMethod() =
+ testEvent(VolumeControllerEvent.VolumeChanged(3, 0)) {
+ verify(volumeController) { 1 * { volumeController.volumeChanged(eq(3), eq(0)) } }
+ }
+
+ @Test
+ fun volumeControllerEvent_dismiss_callsMethod() =
+ testEvent(VolumeControllerEvent.Dismiss) {
+ verify(volumeController) { 1 * { volumeController.dismiss() } }
+ }
+
+ @Test
+ fun volumeControllerEvent_displayCsdWarning_callsMethod() =
+ testEvent(VolumeControllerEvent.DisplayCsdWarning(0, 1)) {
+ verify(volumeController) { 1 * { volumeController.displayCsdWarning(eq(0), eq(1)) } }
+ }
+
+ @Test
+ fun volumeControllerEvent_displaySafeVolumeWarning_callsMethod() =
+ testEvent(VolumeControllerEvent.DisplaySafeVolumeWarning(1)) {
+ verify(volumeController) { 1 * { volumeController.displaySafeVolumeWarning(eq(1)) } }
+ }
+
+ @Test
+ fun volumeControllerEvent_masterMuteChanged_callsMethod() =
+ testEvent(VolumeControllerEvent.MasterMuteChanged(1)) {
+ verify(volumeController) { 1 * { volumeController.masterMuteChanged(1) } }
+ }
+
+ @Test
+ fun volumeControllerEvent_setA11yMode_callsMethod() =
+ testEvent(VolumeControllerEvent.SetA11yMode(1)) {
+ verify(volumeController) { 1 * { volumeController.setA11yMode(1) } }
+ }
+
+ @Test
+ fun volumeControllerEvent_SetLayoutDirection_callsMethod() =
+ testEvent(VolumeControllerEvent.SetLayoutDirection(1)) {
+ verify(volumeController) { 1 * { volumeController.setLayoutDirection(eq(1)) } }
+ }
+
+ private fun testEvent(event: VolumeControllerEvent, verify: () -> Unit) =
+ kosmos.testScope.runTest {
+ underTest.collectToController(eventsFlow.filterNotNull(), volumeController)
+
+ eventsFlow.value = event
+ runCurrent()
+
+ verify()
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
index 8901314..9d7d916 100644
--- a/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
@@ -17,6 +17,13 @@
import android.content.pm.PackageManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.system.PackageManagerWrapper
import com.android.systemui.util.mockito.mock
-val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
+val Kosmos.mockPackageManager by Kosmos.Fixture { mock<PackageManager>() }
+
+var Kosmos.packageManager by Kosmos.Fixture { mockPackageManager }
+
+val Kosmos.mockPackageManagerWrapper by Kosmos.Fixture { mock<PackageManagerWrapper>() }
+
+var Kosmos.packageManagerWrapper by Kosmos.Fixture { mockPackageManagerWrapper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
index b7d6f3a..22eb646 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
@@ -19,4 +19,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.assistManager by Kosmos.Fixture { mock<AssistManager>() }
+val Kosmos.mockAssistManager by Kosmos.Fixture { mock<AssistManager>() }
+
+var Kosmos.assistManager by Kosmos.Fixture { mockAssistManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 55c803a..a1021f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -100,6 +100,7 @@
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
ShortcutHelperViewModel(
+ applicationCoroutineScope,
testDispatcher,
shortcutHelperStateInteractor,
shortcutHelperCategoriesInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 24e47b0..550ecb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
@@ -34,5 +35,6 @@
shadeInteractor = shadeInteractor,
applicationScope = applicationCoroutineScope,
unfoldTransitionInteractor = unfoldTransitionInteractor,
+ occlusionInteractor = sceneContainerOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
new file mode 100644
index 0000000..8aa7a03
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.mediarouter.data.repository
+
+import com.android.systemui.statusbar.policy.CastDevice
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMediaRouterRepository : MediaRouterRepository {
+ override val castDevices: MutableStateFlow<List<CastDevice>> = MutableStateFlow(emptyList())
+
+ var lastStoppedDevice: CastDevice? = null
+ private set
+
+ override fun stopCasting(device: CastDevice) {
+ lastStoppedDevice = device
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
new file mode 100644
index 0000000..eec9920
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.mediarouter.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.fakeCastController
+
+val Kosmos.realMediaRouterRepository by
+ Kosmos.Fixture {
+ MediaRouterRepositoryImpl(
+ scope = applicationCoroutineScope,
+ castController = fakeCastController,
+ )
+ }
+
+val Kosmos.fakeMediaRouterRepository by Kosmos.Fixture { FakeMediaRouterRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
new file mode 100644
index 0000000..cb18b68
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.statusbar.chips.casttootherdevice.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+
+val Kosmos.mediaRouterChipInteractor by
+ Kosmos.Fixture {
+ MediaRouterChipInteractor(
+ scope = applicationCoroutineScope,
+ mediaRouterRepository = fakeMediaRouterRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 4baa8d0..a8de460 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.animation.mockDialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.util.time.fakeSystemClock
@@ -28,6 +29,7 @@
CastToOtherDeviceChipViewModel(
scope = applicationCoroutineScope,
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
+ mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
dialogTransitionAnimator = mockDialogTransitionAnimator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/CastControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/CastControllerKosmos.kt
new file mode 100644
index 0000000..8e77437
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/CastControllerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.statusbar.policy
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCastController: FakeCastController by Kosmos.Fixture { FakeCastController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
new file mode 100644
index 0000000..2df0c7a5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.statusbar.policy
+
+import java.io.PrintWriter
+
+class FakeCastController : CastController {
+ private var listeners = mutableListOf<CastController.Callback>()
+
+ private var castDevices = emptyList<CastDevice>()
+
+ var lastStoppedDevice: CastDevice? = null
+ private set
+
+ override fun addCallback(listener: CastController.Callback) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: CastController.Callback) {
+ listeners -= listener
+ }
+
+ override fun getCastDevices(): List<CastDevice> {
+ return castDevices
+ }
+
+ fun setCastDevices(devices: List<CastDevice>) {
+ castDevices = devices
+ listeners.forEach { it.onCastDevicesChanged() }
+ }
+
+ override fun startCasting(device: CastDevice?) {}
+
+ override fun stopCasting(device: CastDevice?) {
+ lastStoppedDevice = device
+ }
+
+ override fun hasConnectedCastDevice(): Boolean {
+ return castDevices.any { it.state == CastDevice.CastState.Connected }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {}
+
+ override fun setDiscovering(request: Boolean) {}
+
+ override fun setCurrentUserId(currentUserId: Int) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt
new file mode 100644
index 0000000..7987185
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.content.mockPackageManager
+import android.content.mockPackageManagerWrapper
+import com.android.systemui.assist.mockAssistManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+
+var Kosmos.fakeAppCategoryIconProvider by Kosmos.Fixture { FakeAppCategoryIconProvider() }
+
+var Kosmos.appCategoryIconProvider: AppCategoryIconProvider by
+ Kosmos.Fixture {
+ AppCategoryIconProviderImpl(
+ testDispatcher,
+ mockAssistManager,
+ mockPackageManager,
+ mockPackageManagerWrapper
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt
new file mode 100644
index 0000000..3e7bf21
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.graphics.drawable.Icon
+
+class FakeAppCategoryIconProvider : AppCategoryIconProvider {
+
+ private val installedApps = mutableMapOf<String, App>()
+
+ fun installCategoryApp(category: String, packageName: String, iconResId: Int) {
+ installedApps[category] = App(packageName, iconResId)
+ }
+
+ fun installAssistantApp(packageName: String, iconResId: Int) {
+ installedApps[ROLE_ASSISTANT] = App(packageName, iconResId)
+ }
+
+ override suspend fun assistantAppIcon() = categoryAppIcon(ROLE_ASSISTANT)
+
+ override suspend fun categoryAppIcon(category: String): Icon? {
+ val app = installedApps[category] ?: return null
+ return Icon.createWithResource(app.packageName, app.iconResId)
+ }
+
+ private class App(val packageName: String, val iconResId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index 5d21ddd..372a7c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -109,7 +109,7 @@
} else if (cls == ZenModeController.class) {
obj = new FakeZenModeController(this);
} else if (cls == CastController.class) {
- obj = new FakeCastController(this);
+ obj = new LeakCheckerCastController(this);
} else if (cls == HotspotController.class) {
obj = new FakeHotspotController(this);
} else if (cls == FlashlightController.class) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
index 5fae38f..2249bc0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
@@ -1,15 +1,17 @@
/*
* Copyright (C) 2016 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
+ * 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.
+ * 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.utils.leaks;
@@ -23,8 +25,8 @@
import java.util.ArrayList;
import java.util.List;
-public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
- public FakeCastController(LeakCheck test) {
+public class LeakCheckerCastController extends BaseLeakChecker<Callback> implements CastController {
+ public LeakCheckerCastController(LeakCheck test) {
super(test, "cast");
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
new file mode 100644
index 0000000..d60f14c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.volume
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.volumeControllerCollector by
+ Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 4860a27..8abbe56 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -792,10 +792,11 @@
}
private String getVToUAllowlist(Context context, int userId) {
- return Settings.Secure.getStringForUser(
+ String allowlist = Settings.Secure.getStringForUser(
context.getContentResolver(),
Settings.Secure.V_TO_U_RESTORE_ALLOWLIST,
userId);
+ return (allowlist == null) ? "" : allowlist;
}
private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
diff --git a/services/core/java/com/android/server/CertBlacklister.java b/services/core/java/com/android/server/CertBlacklister.java
deleted file mode 100644
index e726c6a..0000000
--- a/services/core/java/com/android/server/CertBlacklister.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2012 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;
-
-import android.content.Context;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.os.Binder;
-import android.os.FileUtils;
-import android.provider.Settings;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import libcore.io.IoUtils;
-
-/**
- * <p>CertBlacklister provides a simple mechanism for updating the platform denylists for SSL
- * certificate public keys and serial numbers.
- */
-public class CertBlacklister extends Binder {
-
- private static final String TAG = "CertBlacklister";
-
- private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
-
- public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt";
- public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt";
-
- public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist";
- public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist";
-
- private static class BlacklistObserver extends ContentObserver {
-
- private final String mKey;
- private final String mName;
- private final String mPath;
- private final File mTmpDir;
- private final ContentResolver mContentResolver;
-
- public BlacklistObserver(String key, String name, String path, ContentResolver cr) {
- super(null);
- mKey = key;
- mName = name;
- mPath = path;
- mTmpDir = new File(mPath).getParentFile();
- mContentResolver = cr;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- writeDenylist();
- }
-
- public String getValue() {
- return Settings.Secure.getString(mContentResolver, mKey);
- }
-
- private void writeDenylist() {
- new Thread("BlacklistUpdater") {
- public void run() {
- synchronized(mTmpDir) {
- String blacklist = getValue();
- if (blacklist != null) {
- Slog.i(TAG, "Certificate blacklist changed, updating...");
- FileOutputStream out = null;
- try {
- // create a temporary file
- File tmp = File.createTempFile("journal", "", mTmpDir);
- // mark it -rw-r--r--
- tmp.setReadable(true, false);
- // write to it
- out = new FileOutputStream(tmp);
- out.write(blacklist.getBytes());
- // sync to disk
- FileUtils.sync(out);
- // atomic rename
- tmp.renameTo(new File(mPath));
- Slog.i(TAG, "Certificate blacklist updated");
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write blacklist", e);
- } finally {
- IoUtils.closeQuietly(out);
- }
- }
- }
- }
- }.start();
- }
- }
-
- public CertBlacklister(Context context) {
- registerObservers(context.getContentResolver());
- }
-
- private BlacklistObserver buildPubkeyObserver(ContentResolver cr) {
- return new BlacklistObserver(PUBKEY_BLACKLIST_KEY,
- "pubkey",
- PUBKEY_PATH,
- cr);
- }
-
- private BlacklistObserver buildSerialObserver(ContentResolver cr) {
- return new BlacklistObserver(SERIAL_BLACKLIST_KEY,
- "serial",
- SERIAL_PATH,
- cr);
- }
-
- private void registerObservers(ContentResolver cr) {
- // set up the public key denylist observer
- cr.registerContentObserver(
- Settings.Secure.getUriFor(PUBKEY_BLACKLIST_KEY),
- true,
- buildPubkeyObserver(cr)
- );
-
- // set up the serial number denylist observer
- cr.registerContentObserver(
- Settings.Secure.getUriFor(SERIAL_BLACKLIST_KEY),
- true,
- buildSerialObserver(cr)
- );
- }
-}
diff --git a/services/core/java/com/android/server/CertBlocklister.java b/services/core/java/com/android/server/CertBlocklister.java
new file mode 100644
index 0000000..9e23f88
--- /dev/null
+++ b/services/core/java/com/android/server/CertBlocklister.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.provider.Settings;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * <p>CertBlocklister provides a simple mechanism for updating the platform denylists for SSL
+ * certificate public keys and serial numbers.
+ */
+public class CertBlocklister extends Binder {
+
+ private static final String TAG = "CertBlocklister";
+
+ private static final String DENYLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+ /* For compatibility reasons, the name of these paths cannot be changed */
+ public static final String PUBKEY_PATH = DENYLIST_ROOT + "pubkey_blacklist.txt";
+ public static final String SERIAL_PATH = DENYLIST_ROOT + "serial_blacklist.txt";
+
+ /* For compatibility reasons, the name of these keys cannot be changed */
+ public static final String PUBKEY_BLOCKLIST_KEY = "pubkey_blacklist";
+ public static final String SERIAL_BLOCKLIST_KEY = "serial_blacklist";
+
+ private static class BlocklistObserver extends ContentObserver {
+
+ private final String mKey;
+ private final String mName;
+ private final String mPath;
+ private final File mTmpDir;
+ private final ContentResolver mContentResolver;
+
+ BlocklistObserver(String key, String name, String path, ContentResolver cr) {
+ super(null);
+ mKey = key;
+ mName = name;
+ mPath = path;
+ mTmpDir = new File(mPath).getParentFile();
+ mContentResolver = cr;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ new Thread("BlocklistUpdater") {
+ public void run() {
+ writeDenylist();
+ }
+ }.start();
+ }
+
+ public String getValue() {
+ return Settings.Secure.getStringForUser(
+ mContentResolver, mKey, mContentResolver.getUserId());
+ }
+
+ private void writeDenylist() {
+ synchronized (mTmpDir) {
+ String blocklist = getValue();
+ if (blocklist == null) {
+ return;
+ }
+ if (mPath.equals(SERIAL_PATH)) {
+ Slog.w(TAG, "The certificate blocklist based on serials is deprecated. "
+ + "Please use the pubkey blocklist instead.");
+ }
+ Slog.i(TAG, "Certificate blocklist changed, updating...");
+ FileOutputStream out = null;
+ try {
+ // Create a temporary file and rename it atomically.
+ File tmp = File.createTempFile("journal", "", mTmpDir);
+ tmp.setReadable(true /* readable */, false /* ownerOnly */);
+ out = new FileOutputStream(tmp);
+ out.write(blocklist.getBytes());
+ FileUtils.sync(out);
+ tmp.renameTo(new File(mPath));
+ Slog.i(TAG, "Certificate blocklist updated");
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write blocklist", e);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+ }
+ }
+
+ public CertBlocklister(Context context) {
+ registerObservers(context.getContentResolver());
+ }
+
+ private BlocklistObserver buildPubkeyObserver(ContentResolver cr) {
+ return new BlocklistObserver(PUBKEY_BLOCKLIST_KEY,
+ "pubkey",
+ PUBKEY_PATH,
+ cr);
+ }
+
+ private BlocklistObserver buildSerialObserver(ContentResolver cr) {
+ return new BlocklistObserver(SERIAL_BLOCKLIST_KEY,
+ "serial",
+ SERIAL_PATH,
+ cr);
+ }
+
+ private void registerObservers(ContentResolver cr) {
+ // set up the public key denylist observer
+ cr.registerContentObserver(
+ Settings.Secure.getUriFor(PUBKEY_BLOCKLIST_KEY),
+ true,
+ buildPubkeyObserver(cr)
+ );
+
+ // set up the serial number denylist observer
+ cr.registerContentObserver(
+ Settings.Secure.getUriFor(SERIAL_BLOCKLIST_KEY),
+ true,
+ buildSerialObserver(cr)
+ );
+ }
+}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bc83a0e..bacfd8f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -921,8 +921,7 @@
//helper function to determine if limit on num listeners applies to callingUid
private boolean doesLimitApplyForListeners(int callingUid, int exemptUid) {
- return (callingUid != Process.SYSTEM_UID
- && callingUid != Process.PHONE_UID
+ return (!TelephonyPermissions.isSystemOrPhone(callingUid)
&& callingUid != exemptUid);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fbb6ccf..b0d734d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -70,7 +70,6 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -80,7 +79,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManagerInternal;
@@ -195,7 +193,6 @@
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -349,8 +346,6 @@
@GuardedBy("ImfLock.class")
private UserDataRepository mUserDataRepository;
- @MultiUserUnawareField
- final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
final PackageManagerInternal mPackageManagerInternal;
@@ -570,82 +565,52 @@
@NonNull
private final ImeTrackerService mImeTrackerService;
- class SettingsObserver extends ContentObserver {
-
- /**
- * <em>This constructor must be called within the lock.</em>
- */
- SettingsObserver(Handler handler) {
- super(handler);
+ @GuardedBy("ImfLock.class")
+ private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
+ if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+ return;
}
-
- void registerContentObserverForAllUsers() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- Settings.Secure.DEFAULT_INPUT_METHOD), false, this, UserHandle.ALL);
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_INPUT_METHODS), false, this, UserHandle.ALL);
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, UserHandle.ALL);
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, UserHandle.ALL);
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, UserHandle.ALL);
- resolver.registerContentObserverAsUser(Settings.Secure.getUriFor(
- STYLUS_HANDWRITING_ENABLED), false, this, UserHandle.ALL);
- }
-
- @Override
- public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags,
- @UserIdInt int userId) {
- uris.forEach(uri -> onChangeInternal(uri, userId));
- }
-
- private void onChangeInternal(@NonNull Uri uri, @UserIdInt int userId) {
- final Uri showImeUri = Settings.Secure.getUriFor(
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
- final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
- final Uri stylusHandwritingEnabledUri = Settings.Secure.getUriFor(
- STYLUS_HANDWRITING_ENABLED);
- synchronized (ImfLock.class) {
- if (!mConcurrentMultiUserModeEnabled && mCurrentUserId != userId) {
- return;
+ switch (key) {
+ case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
+ mMenuController.updateKeyboardFromSettingsLocked();
+ break;
+ }
+ case Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE: {
+ final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId);
+ mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
+ accessibilitySoftKeyboardSetting);
+ final var userData = getUserData(userId);
+ if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId);
+ } else if (isShowRequestedForCurrentWindow(userId)) {
+ showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ InputMethodManager.SHOW_IMPLICIT,
+ SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
}
-
- if (showImeUri.equals(uri)) {
- mMenuController.updateKeyboardFromSettingsLocked();
- } else if (accessibilityRequestingNoImeUri.equals(uri)) {
- final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId);
- mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
- accessibilitySoftKeyboardSetting);
- final var userData = getUserData(userId);
- if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
- userId);
- } else if (isShowRequestedForCurrentWindow(userId)) {
- showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- InputMethodManager.SHOW_IMPLICIT,
- SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId);
- }
- } else if (stylusHandwritingEnabledUri.equals(uri)) {
- InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
- InputMethodManager
- .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
- } else {
- boolean enabledChanged = false;
- String newEnabled = InputMethodSettingsRepository.get(userId)
- .getEnabledInputMethodsStr();
- final var userData = getUserData(userId);
- if (!userData.mLastEnabledInputMethodsStr.equals(newEnabled)) {
- userData.mLastEnabledInputMethodsStr = newEnabled;
- enabledChanged = true;
- }
- updateInputMethodsFromSettingsLocked(enabledChanged, userId);
+ break;
+ }
+ case STYLUS_HANDWRITING_ENABLED: {
+ InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+ InputMethodManager
+ .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
+ break;
+ }
+ case Settings.Secure.DEFAULT_INPUT_METHOD:
+ case Settings.Secure.ENABLED_INPUT_METHODS:
+ case Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE: {
+ boolean enabledChanged = false;
+ String newEnabled = InputMethodSettingsRepository.get(userId)
+ .getEnabledInputMethodsStr();
+ final var userData = getUserData(userId);
+ if (!userData.mLastEnabledInputMethodsStr.equals(newEnabled)) {
+ userData.mLastEnabledInputMethodsStr = newEnabled;
+ enabledChanged = true;
}
+ updateInputMethodsFromSettingsLocked(enabledChanged, userId);
+ break;
}
}
}
@@ -1118,8 +1083,6 @@
}
SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
mImeTrackerService = new ImeTrackerService(mHandler);
- // Note: SettingsObserver doesn't register observers in its constructor.
- mSettingsObserver = new SettingsObserver(mHandler);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
@@ -1389,7 +1352,19 @@
}, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
- mSettingsObserver.registerContentObserverForAllUsers();
+ SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
+ new String[] {
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ Settings.Secure.ENABLED_INPUT_METHODS,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ Settings.Secure.STYLUS_HANDWRITING_ENABLED,
+ }, (key, flags, userId) -> {
+ synchronized (ImfLock.class) {
+ onSecureSettingsChangedLocked(key, userId);
+ }
+ });
final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsChangeCallback.java b/services/core/java/com/android/server/inputmethod/SecureSettingsChangeCallback.java
new file mode 100644
index 0000000..328d7c6
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsChangeCallback.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import java.util.Collection;
+
+/**
+ * A wrapper interface to monitor the given set of {@link Settings.Secure}.
+ */
+@FunctionalInterface
+interface SecureSettingsChangeCallback {
+ /**
+ * Called back when the value associated with {@code key} is updated.
+ *
+ * @param key a key defined in {@link Settings.Secure}
+ * @param flags flags defined in {@link ContentResolver.NotifyFlags}
+ * @param userId the user ID with which the value is associated
+ */
+ void onChange(@NonNull String key, @ContentResolver.NotifyFlags int flags,
+ @UserIdInt int userId);
+
+ /**
+ * Registers {@link SecureSettingsChangeCallback} to the given set of {@link Settings.Secure}.
+ *
+ * @param handler {@link Handler} to be used to call back {@link #onChange(String, int, int)}
+ * @param resolver {@link ContentResolver} with which {@link Settings.Secure} will be retrieved
+ * @param keys A set of {@link Settings.Secure} to be monitored
+ * @param callback {@link SecureSettingsChangeCallback} to be called back
+ */
+ @NonNull
+ static void register(@NonNull Handler handler, @NonNull ContentResolver resolver,
+ @NonNull String[] keys, @NonNull SecureSettingsChangeCallback callback) {
+ final ArrayMap<Uri, String> uriMapper = new ArrayMap<>();
+ for (String key : keys) {
+ uriMapper.put(Settings.Secure.getUriFor(key), key);
+ }
+ final ContentObserver observer = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags,
+ @UserIdInt int userId) {
+ uris.forEach(uri -> {
+ final String key = uriMapper.get(uri);
+ if (key != null) {
+ callback.onChange(key, flags, userId);
+ }
+ });
+ }
+ };
+ for (Uri uri : uriMapper.keySet()) {
+ resolver.registerContentObserverAsUser(uri, false /* notifyForDescendants */, observer,
+ UserHandle.ALL);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 3a0c1d0..c09504f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -196,9 +196,12 @@
int USER_LOCKED_BUBBLE = 0x00000002;
}
+ private final Object mLock = new Object();
// pkg|uid => PackagePreferences
+ @GuardedBy("mLock")
private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
// pkg|userId => PackagePreferences
+ @GuardedBy("mLock")
private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
private final Context mContext;
@@ -270,7 +273,7 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
}
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -492,6 +495,7 @@
DEFAULT_BUBBLE_PREFERENCE, mClock.millis());
}
+ @GuardedBy("mLock")
private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
@UserIdInt int userId, int uid, int importance, int priority, int visibility,
boolean showBadge, int bubblePreference, long creationTime) {
@@ -661,7 +665,7 @@
notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
}
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final int N = mPackagePreferences.size();
for (int i = 0; i < N; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
@@ -670,11 +674,10 @@
}
writePackageXml(r, out, notifPermissions, forBackup);
}
- }
- if (Flags.persistIncompleteRestoreData() && !forBackup) {
- synchronized (mRestoredWithoutUids) {
- final int N = mRestoredWithoutUids.size();
- for (int i = 0; i < N; i++) {
+
+ if (Flags.persistIncompleteRestoreData() && !forBackup) {
+ final int M = mRestoredWithoutUids.size();
+ for (int i = 0; i < M; i++) {
final PackagePreferences r = mRestoredWithoutUids.valueAt(i);
writePackageXml(r, out, notifPermissions, false);
}
@@ -777,7 +780,7 @@
*/
public void setBubblesAllowed(String pkg, int uid, int bubblePreference) {
boolean changed;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
changed = p.bubblePreference != bubblePreference;
p.bubblePreference = bubblePreference;
@@ -797,20 +800,20 @@
*/
@Override
public int getBubblePreference(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference;
}
}
public int getAppLockedFields(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields;
}
}
@Override
public boolean canShowBadge(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;
}
}
@@ -818,7 +821,7 @@
@Override
public void setShowBadge(String packageName, int uid, boolean showBadge) {
boolean changed = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
if (pkgPrefs.showBadge != showBadge) {
pkgPrefs.showBadge = showBadge;
@@ -831,28 +834,28 @@
}
public boolean isInInvalidMsgState(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return r.hasSentInvalidMessage && !r.hasSentValidMessage;
}
}
public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false;
}
}
public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
r.userDemotedMsgApp = isDemoted;
}
}
public boolean setInvalidMessageSent(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
boolean valueChanged = r.hasSentInvalidMessage == false;
r.hasSentInvalidMessage = true;
@@ -862,7 +865,7 @@
}
public boolean setValidMessageSent(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
boolean valueChanged = r.hasSentValidMessage == false;
r.hasSentValidMessage = true;
@@ -873,7 +876,7 @@
@VisibleForTesting
boolean hasSentInvalidMsg(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return r.hasSentInvalidMessage;
}
@@ -881,7 +884,7 @@
@VisibleForTesting
boolean hasSentValidMsg(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return r.hasSentValidMessage;
}
@@ -889,7 +892,7 @@
@VisibleForTesting
boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return r.userDemotedMsgApp;
}
@@ -897,7 +900,7 @@
/** Sets whether this package has sent a notification with valid bubble metadata. */
public boolean setValidBubbleSent(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
boolean valueChanged = !r.hasSentValidBubble;
r.hasSentValidBubble = true;
@@ -906,14 +909,14 @@
}
boolean hasSentValidBubble(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
return r.hasSentValidBubble;
}
}
boolean isImportanceLocked(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
return r.fixedImportance || r.defaultAppLockedImportance;
}
@@ -924,7 +927,7 @@
if (groupId == null) {
return false;
}
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
NotificationChannelGroup group = r.groups.get(groupId);
if (group == null) {
@@ -935,13 +938,13 @@
}
int getPackagePriority(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(pkg, uid).priority;
}
}
int getPackageVisibility(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
return getOrCreatePackagePreferencesLocked(pkg, uid).visibility;
}
}
@@ -956,7 +959,7 @@
throw new IllegalArgumentException("group.getName() can't be empty");
}
boolean needsDndChange = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
@@ -1010,7 +1013,7 @@
&& channel.getImportance() <= IMPORTANCE_MAX, "Invalid importance level");
boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
@@ -1154,7 +1157,7 @@
void unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId) {
Objects.requireNonNull(updatedChannelId);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
@@ -1176,7 +1179,7 @@
Objects.requireNonNull(updatedChannel.getId());
boolean changed = false;
boolean needsDndChange = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
@@ -1351,7 +1354,7 @@
String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
return null;
@@ -1392,7 +1395,7 @@
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(conversationId);
List<NotificationChannel> channels = new ArrayList<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
return channels;
@@ -1412,7 +1415,7 @@
int callingUid, boolean fromSystemOrSystemUi) {
boolean deletedChannel = false;
boolean channelBypassedDnd = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return false;
@@ -1448,7 +1451,7 @@
public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
Objects.requireNonNull(pkg);
Objects.requireNonNull(channelId);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return;
@@ -1460,7 +1463,7 @@
@Override
public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
Objects.requireNonNull(pkg);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return;
@@ -1491,7 +1494,7 @@
boolean fixed = mPermissionHelper.isPermissionFixed(
pi.packageName, user.getUserHandle().getIdentifier());
if (fixed) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences p = getOrCreatePackagePreferencesLocked(
pi.packageName, pi.applicationInfo.uid);
p.fixedImportance = true;
@@ -1506,7 +1509,7 @@
public void updateDefaultApps(int userId, ArraySet<String> toRemove,
ArraySet<Pair<String, Integer>> toAdd) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
for (PackagePreferences p : mPackagePreferences.values()) {
if (userId == UserHandle.getUserId(p.uid)) {
if (toRemove != null && toRemove.contains(p.pkg)) {
@@ -1536,7 +1539,7 @@
public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
int uid, String groupId, boolean includeDeleted) {
Objects.requireNonNull(pkg);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
return null;
@@ -1559,7 +1562,7 @@
public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
int uid) {
Objects.requireNonNull(pkg);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return null;
@@ -1573,7 +1576,7 @@
boolean includeBlocked, Set<String> activeChannelFilter) {
Objects.requireNonNull(pkg);
Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
@@ -1624,7 +1627,7 @@
String groupId, int callingUid, boolean fromSystemOrSystemUi) {
List<NotificationChannel> deletedChannels = new ArrayList<>();
boolean groupBypassedDnd = false;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null || TextUtils.isEmpty(groupId)) {
return deletedChannels;
@@ -1656,7 +1659,7 @@
public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid) {
List<NotificationChannelGroup> groups = new ArrayList<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return groups;
@@ -1667,7 +1670,7 @@
}
public NotificationChannelGroup getGroupForChannel(String pkg, int uid, String channelId) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
if (p != null) {
NotificationChannel nc = p.channels.get(channelId);
@@ -1686,7 +1689,7 @@
public ArrayList<ConversationChannelWrapper> getConversations(IntArray userIds,
boolean onlyImportant) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
for (PackagePreferences p : mPackagePreferences.values()) {
if (userIds.binarySearch(UserHandle.getUserId(p.uid)) >= 0) {
@@ -1730,7 +1733,7 @@
public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) {
Objects.requireNonNull(pkg);
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return new ArrayList<>();
@@ -1772,7 +1775,7 @@
public @NonNull List<String> deleteConversations(String pkg, int uid,
Set<String> conversationIds, int callingUid, boolean fromSystemOrSystemUi) {
List<String> deletedChannelIds = new ArrayList<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return deletedChannelIds;
@@ -1805,7 +1808,7 @@
boolean includeDeleted) {
Objects.requireNonNull(pkg);
List<NotificationChannel> channels = new ArrayList<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
@@ -1827,7 +1830,7 @@
public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
int uid) {
List<NotificationChannel> channels = new ArrayList<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final PackagePreferences r = mPackagePreferences.get(
packagePreferencesKey(pkg, uid));
if (r != null) {
@@ -1848,7 +1851,7 @@
* upgrades.
*/
public boolean onlyHasDefaultChannel(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r.channels.size() == (notificationClassification() ? 5 : 1)
&& r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
@@ -1861,7 +1864,7 @@
public int getDeletedChannelCount(String pkg, int uid) {
Objects.requireNonNull(pkg);
int deletedCount = 0;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return deletedCount;
@@ -1880,7 +1883,7 @@
public int getBlockedChannelCount(String pkg, int uid) {
Objects.requireNonNull(pkg);
int blockedCount = 0;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return blockedCount;
@@ -1923,7 +1926,7 @@
ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final int numPackagePreferences = mPackagePreferences.size();
for (int i = 0; i < numPackagePreferences; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
@@ -1992,7 +1995,7 @@
* considered for sentiment adjustments (and thus never show a blocking helper).
*/
public void setAppImportanceLocked(String packageName, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid);
if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
return;
@@ -2008,7 +2011,7 @@
* Returns the delegate for a given package, if it's allowed by the package and the user.
*/
public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
if (prefs == null || prefs.delegate == null) {
@@ -2026,7 +2029,7 @@
*/
public void setNotificationDelegate(String sourcePkg, int sourceUid,
String delegatePkg, int delegateUid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
}
@@ -2036,7 +2039,7 @@
* Used by an app to turn off its notification delegate.
*/
public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
if (prefs != null && prefs.delegate != null) {
prefs.delegate.mEnabled = false;
@@ -2050,7 +2053,7 @@
*/
public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
String potentialDelegatePkg, int potentialDelegateUid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
return prefs != null && prefs.isValidDelegate(potentialDelegatePkg,
@@ -2096,24 +2099,25 @@
pw.println("per-package config version: " + XML_VERSION);
pw.println("PackagePreferences:");
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions);
+ pw.println("Restored without uid:");
+ dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null);
}
- pw.println("Restored without uid:");
- dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null);
}
public void dump(ProtoOutputStream proto,
@NonNull NotificationManagerService.DumpFilter filter,
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
mPackagePreferences, pkgPermissions);
+ dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID,
+ filter, mRestoredWithoutUids, null);
}
- dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
- mRestoredWithoutUids, null);
}
+ @GuardedBy("mLock")
private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
@NonNull NotificationManagerService.DumpFilter filter,
ArrayMap<String, PackagePreferences> packagePreferences,
@@ -2298,7 +2302,7 @@
pkgsWithPermissionsToHandle = pkgPermissions.keySet();
}
int pulledEvents = 0;
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
for (int i = 0; i < mPackagePreferences.size(); i++) {
if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) {
break;
@@ -2378,7 +2382,7 @@
* {@link StatsEvent}.
*/
public void pullPackageChannelPreferencesStats(List<StatsEvent> events) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
int totalChannelsPulled = 0;
for (int i = 0; i < mPackagePreferences.size(); i++) {
if (totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
@@ -2414,7 +2418,7 @@
* {@link StatsEvent}.
*/
public void pullPackageChannelGroupPreferencesStats(List<StatsEvent> events) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
int totalGroupsPulled = 0;
for (int i = 0; i < mPackagePreferences.size(); i++) {
if (totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
@@ -2443,10 +2447,12 @@
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
JSONObject ranking = new JSONObject();
JSONArray PackagePreferencess = new JSONArray();
- try {
- ranking.put("noUid", mRestoredWithoutUids.size());
- } catch (JSONException e) {
- // pass
+ synchronized (mLock) {
+ try {
+ ranking.put("noUid", mRestoredWithoutUids.size());
+ } catch (JSONException e) {
+ // pass
+ }
}
// Track data that we've handled from the permissions-based list
@@ -2455,7 +2461,7 @@
pkgsWithPermissionsToHandle = pkgPermissions.keySet();
}
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final int N = mPackagePreferences.size();
for (int i = 0; i < N; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
@@ -2561,7 +2567,7 @@
}
public Map<Integer, String> getPackageBans() {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final int N = mPackagePreferences.size();
ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
for (int i = 0; i < N; i++) {
@@ -2620,7 +2626,7 @@
private Map<String, Integer> getPackageChannels() {
ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
for (int i = 0; i < mPackagePreferences.size(); i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
int channelCount = 0;
@@ -2636,7 +2642,7 @@
}
public void onUserRemoved(int userId) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
int N = mPackagePreferences.size();
for (int i = N - 1; i >= 0; i--) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
@@ -2648,7 +2654,7 @@
}
protected void onLocaleChanged(Context context, int userId) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
int N = mPackagePreferences.size();
for (int i = 0; i < N; i++) {
PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
@@ -2678,22 +2684,24 @@
for (int i = 0; i < size; i++) {
final String pkg = pkgList[i];
final int uid = uidList[i];
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
+ mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
}
- mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
updated = true;
}
} else {
for (String pkg : pkgList) {
- // Package install
- final PackagePreferences r =
- mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
- if (r != null) {
- try {
- r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
- mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
- synchronized (mPackagePreferences) {
+ try {
+ // Package install
+ int uid = mPm.getPackageUidAsUser(pkg, changeUserId);
+ PackagePermission p = null;
+ synchronized (mLock) {
+ final PackagePreferences r =
+ mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
+ if (r != null) {
+ r.uid = uid;
+ mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
// Try to restore any unrestored sound resources
@@ -2715,32 +2723,29 @@
channel.setSound(restoredUri, channel.getAudioAttributes());
}
}
- }
- if (r.migrateToPm) {
- try {
- PackagePermission p = new PackagePermission(
+
+ if (r.migrateToPm) {
+ p = new PackagePermission(
r.pkg, UserHandle.getUserId(r.uid),
r.importance != IMPORTANCE_NONE,
hasUserConfiguredSettings(r));
- mPermissionHelper.setNotificationPermission(p);
- } catch (Exception e) {
- Slog.e(TAG, "could not migrate setting for " + r.pkg, e);
}
+ updated = true;
}
- updated = true;
- } catch (Exception e) {
- Slog.e(TAG, "could not restore " + r.pkg, e);
}
+ if (p != null) {
+ mPermissionHelper.setNotificationPermission(p);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "could not restore " + pkg, e);
}
// Package upgrade
try {
- synchronized (mPackagePreferences) {
- PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg,
- mPm.getPackageUidAsUser(pkg, changeUserId));
- if (fullPackagePreferences != null) {
- updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences);
- updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences);
- }
+ PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg,
+ mPm.getPackageUidAsUser(pkg, changeUserId));
+ if (fullPackagePreferences != null) {
+ updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences);
+ updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences);
}
} catch (PackageManager.NameNotFoundException e) {
}
@@ -2754,7 +2759,7 @@
}
public void clearData(String pkg, int uid) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
if (p != null) {
p.channels = new ArrayMap<>();
@@ -2941,7 +2946,7 @@
}
public void unlockAllNotificationChannels() {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
final int numPackagePreferences = mPackagePreferences.size();
for (int i = 0; i < numPackagePreferences; i++) {
final PackagePreferences r = mPackagePreferences.valueAt(i);
@@ -2958,7 +2963,7 @@
PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL),
user.getUserHandle().getIdentifier());
for (PackageInfo pi : packages) {
- synchronized (mPackagePreferences) {
+ synchronized (mLock) {
PackagePreferences p = getOrCreatePackagePreferencesLocked(
pi.packageName, pi.applicationInfo.uid);
if (p.migrateToPm && p.uid != UNKNOWN_UID) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c0b8034..2e63cdb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,6 +186,7 @@
import com.android.internal.pm.pkg.component.ParsedMainComponent;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.telephony.CarrierAppUtils;
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -4492,8 +4493,7 @@
void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
boolean hidden) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
- || callingUid == Process.SYSTEM_UID;
+ final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index ff8abf8..924b36c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -92,6 +92,7 @@
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -356,7 +357,7 @@
* If not, throws a {@link SecurityException}.
*/
public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) {
- if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) {
+ if (!TelephonyPermissions.isSystemOrPhone(callingUid)) {
throw new SecurityException(
"Cannot call " + methodName + " from UID " + callingUid);
}
diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
index bf32045..5565b6f 100644
--- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
@@ -47,7 +47,7 @@
private static final String LOGDIR_PREFIX = "logs-";
public CertificateTransparencyLogInstallReceiver() {
- super("/data/misc/keychain/trusted_ct_logs/", "ct_logs", "metadata/", "version");
+ super("/data/misc/keychain/ct/", "ct_logs", "metadata/", "version");
}
@Override
@@ -85,7 +85,7 @@
}
}
try {
- // 3. Create /data/misc/keychain/trusted_ct_logs/<new_version>/ .
+ // 3. Create /data/misc/keychain/ct/<new_version>/ .
newVersion.mkdir();
if (!newVersion.isDirectory()) {
throw new IOException("Unable to make directory " + newVersion.getCanonicalPath());
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 543eb70..129cee7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,6 +53,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -342,6 +343,7 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -2923,14 +2925,10 @@
/** Makes starting window always fill the associated task. */
private void attachStartingSurfaceToAssociatedTask() {
- if (mSyncState == SYNC_STATE_NONE && isEmbedded()) {
- // Collect this activity since it's starting window will reparent to task. To ensure
- // any starting window's transaction will occur in order.
- mTransitionController.collect(this);
- }
+ mTransitionController.collect(mStartingWindow);
// Associate the configuration of starting window with the task.
overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
- getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
+ mStartingWindow.getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
mStartingData.mAssociatedTask.mSurfaceControl);
}
@@ -6537,9 +6535,7 @@
// and the token could be null.
return;
}
- if (r.mDisplayContent.mActivityRefresher != null) {
- r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
- }
+ r.mDisplayContent.mAppCompatCameraPolicy.onActivityRefreshed(r);
}
static void splashScreenAttachedLocked(IBinder token) {
@@ -8196,7 +8192,7 @@
}
void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
- if (mAppCompatController.getAppCompatOrientationOverrides()
+ if (mAppCompatController.getOrientationPolicy()
.shouldIgnoreRequestedOrientation(requestedOrientation)) {
return;
}
@@ -8685,7 +8681,7 @@
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
- applySizeOverrideIfNeeded(newParentConfiguration, resolvedConfig);
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
mResolveConfigHint.resetTmpOverrides();
logAppCompatState();
@@ -8708,15 +8704,85 @@
* TODO: Consider integrate this with computeConfigByResolveHint()
*/
private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
- Configuration inOutConfig) {
- applySizeOverride(
- mDisplayContent,
- info.applicationInfo,
- newParentConfiguration,
- inOutConfig,
- mOptOutEdgeToEdge,
- hasFixedRotationTransform(),
- getCompatDisplayInsets() != null);
+ int parentWindowingMode, Configuration inOutConfig) {
+ if (mDisplayContent == null) {
+ return;
+ }
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
+ rotation = mDisplayContent.getRotation();
+ }
+ if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
+ || getCompatDisplayInsets() != null
+ || (isFloating(parentWindowingMode)
+ // Check the requested windowing mode of activity as well in case it is
+ // switching between PiP and fullscreen.
+ && (inOutConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_UNDEFINED
+ || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
+ || rotation == ROTATION_UNDEFINED)) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+ // Make sure the orientation related fields will be updated by the override insets, because
+ // fixed rotation has assigned the fields from display's configuration.
+ if (hasFixedRotationTransform()) {
+ inOutConfig.windowConfiguration.setAppBounds(null);
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.orientation = ORIENTATION_UNDEFINED;
+ }
+
+ // Override starts here.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(parentBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.inset(nonDecorInsets);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ }
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
+ mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+ mDisplayContent.getDisplayMetrics().density,
+ inOutConfig, true /* overrideConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation =
+ (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
}
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@@ -9952,16 +10018,6 @@
return updateReportedConfigurationAndSend();
}
- /**
- * @return {@code true} if the Camera is active for the current activity
- */
- boolean isCameraActive() {
- return mDisplayContent != null
- && mDisplayContent.getDisplayRotationCompatPolicy() != null
- && mDisplayContent.getDisplayRotationCompatPolicy()
- .isCameraActive(this, /* mustBeFullscreen */ true);
- }
-
boolean updateReportedConfigurationAndSend() {
if (isConfigurationDispatchPaused()) {
Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
@@ -10109,11 +10165,10 @@
private void notifyActivityRefresherAboutConfigurationChange(
Configuration newConfig, Configuration lastReportedConfig) {
- if (mDisplayContent.mActivityRefresher == null
- || !shouldBeResumed(/* activeActivity */ null)) {
+ if (!shouldBeResumed(/* activeActivity */ null)) {
return;
}
- mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
+ mDisplayContent.mAppCompatCameraPolicy.onActivityConfigurationChanging(
this, newConfig, lastReportedConfig);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index c0e5005..0d108e1 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -97,8 +97,7 @@
* </ul>
*/
boolean shouldOverrideMinAspectRatioForCamera() {
- return mActivityRecord.isCameraActive()
- && mAllowMinAspectRatioOverrideOptProp
+ return isCameraActive() && mAllowMinAspectRatioOverrideOptProp
.shouldEnableWithOptInOverrideAndOptOutProperty(
isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
}
@@ -174,6 +173,15 @@
}
/**
+ * @return {@code true} if the Camera is active for the current activity
+ */
+ boolean isCameraActive() {
+ return mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.mAppCompatCameraPolicy
+ .isCameraActive(mActivityRecord, /* mustBeFullscreen */ true);
+ }
+
+ /**
* @return {@code true} if the configuration needs to be recomputed after a camera state update.
*/
boolean shouldRecomputeConfigurationForCameraCompat() {
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index ee523a2..53729a2 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -16,34 +16,158 @@
package com.android.server.wm;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.widget.Toast;
+
+import com.android.window.flags.Flags;
/**
- * Encapsulate the app compat logic related to camera.
+ * Encapsulate policy logic related to app compat display rotation.
*/
class AppCompatCameraPolicy {
- private static final String TAG = TAG_WITH_CLASS_NAME
- ? "AppCompatCameraPolicy" : TAG_ATM;
+ @Nullable
+ private final CameraStateMonitor mCameraStateMonitor;
+ @Nullable
+ private final ActivityRefresher mActivityRefresher;
+ @Nullable
+ final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+ @Nullable
+ final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
- @NonNull
- private final ActivityRecord mActivityRecord;
-
- @NonNull
- private final AppCompatCameraOverrides mAppCompatCameraOverrides;
-
- AppCompatCameraPolicy(@NonNull ActivityRecord activityRecord,
- @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
- mActivityRecord = activityRecord;
- mAppCompatCameraOverrides = appCompatCameraOverrides;
- }
-
- void recomputeConfigurationForCameraCompatIfNeeded() {
- if (mAppCompatCameraOverrides.shouldRecomputeConfigurationForCameraCompat()) {
- mActivityRecord.recomputeConfiguration();
+ AppCompatCameraPolicy(@NonNull WindowManagerService wmService,
+ @NonNull DisplayContent displayContent) {
+ // Not checking DeviceConfig value here to allow enabling via DeviceConfig
+ // without the need to restart the device.
+ final boolean needsDisplayRotationCompatPolicy =
+ wmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
+ final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
+ && DesktopModeLaunchParamsModifier.canEnterDesktopMode(wmService.mContext);
+ if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
+ mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
+ mActivityRefresher = new ActivityRefresher(wmService, wmService.mH);
+ mDisplayRotationCompatPolicy =
+ needsDisplayRotationCompatPolicy ? new DisplayRotationCompatPolicy(
+ displayContent, mCameraStateMonitor, mActivityRefresher) : null;
+ mCameraCompatFreeformPolicy =
+ needsCameraCompatFreeformPolicy ? new CameraCompatFreeformPolicy(displayContent,
+ mCameraStateMonitor, mActivityRefresher) : null;
+ } else {
+ mDisplayRotationCompatPolicy = null;
+ mCameraCompatFreeformPolicy = null;
+ mCameraStateMonitor = null;
+ mActivityRefresher = null;
}
}
+
+ void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ if (mActivityRefresher != null) {
+ mActivityRefresher.onActivityRefreshed(activity);
+ }
+ }
+
+ /**
+ * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ */
+ void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+ if (mActivityRefresher != null) {
+ mActivityRefresher.onActivityConfigurationChanging(activity, newConfig,
+ lastReportedConfig);
+ }
+ }
+
+ /**
+ * Notifies that animation in {@link ScreenRotationAnimation} has finished.
+ *
+ * <p>This class uses this signal as a trigger for notifying the user about forced rotation
+ * reason with the {@link Toast}.
+ */
+ void onScreenRotationAnimationFinished() {
+ if (mDisplayRotationCompatPolicy != null) {
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+ }
+ }
+
+ boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+ if (mDisplayRotationCompatPolicy != null) {
+ return mDisplayRotationCompatPolicy.isActivityEligibleForOrientationOverride(activity);
+ }
+ return false;
+ }
+
+ /**
+ * Whether camera compat treatment is applicable for the given activity.
+ *
+ * <p>Conditions that need to be met:
+ * <ul>
+ * <li>Camera is active for the package.
+ * <li>The activity is in fullscreen
+ * <li>The activity has fixed orientation but not "locked" or "nosensor" one.
+ * </ul>
+ */
+ boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+ if (mDisplayRotationCompatPolicy != null) {
+ return mDisplayRotationCompatPolicy.isTreatmentEnabledForActivity(activity);
+ }
+ return false;
+ }
+
+ void start() {
+ if (mCameraCompatFreeformPolicy != null) {
+ mCameraCompatFreeformPolicy.start();
+ }
+ if (mCameraStateMonitor != null) {
+ mCameraStateMonitor.startListeningToCameraState();
+ }
+ }
+
+ void dispose() {
+ if (mDisplayRotationCompatPolicy != null) {
+ mDisplayRotationCompatPolicy.dispose();
+ }
+ if (mCameraCompatFreeformPolicy != null) {
+ mCameraCompatFreeformPolicy.dispose();
+ }
+ if (mCameraStateMonitor != null) {
+ mCameraStateMonitor.dispose();
+ }
+ }
+
+ boolean hasDisplayRotationCompatPolicy() {
+ return mDisplayRotationCompatPolicy != null;
+ }
+
+ boolean hasCameraCompatFreeformPolicy() {
+ return mCameraCompatFreeformPolicy != null;
+ }
+
+ @ScreenOrientation
+ int getOrientation() {
+ return mDisplayRotationCompatPolicy != null
+ ? mDisplayRotationCompatPolicy.getOrientation()
+ : SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ return mDisplayRotationCompatPolicy != null
+ && mDisplayRotationCompatPolicy.isCameraActive(activity, mustBeFullscreen);
+ }
+
+ @Nullable
+ String getSummaryForDisplayRotationHistoryRecord() {
+ if (mDisplayRotationCompatPolicy != null) {
+ return mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord();
+ }
+ return null;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index d8c0c17..16d3787 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
@@ -26,16 +27,17 @@
class AppCompatController {
@NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
private final TransparentPolicy mTransparentPolicy;
@NonNull
private final AppCompatOrientationPolicy mOrientationPolicy;
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
- @NonNull
- private final AppCompatCameraPolicy mAppCompatCameraPolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
+ mActivityRecord = activityRecord;
final PackageManager packageManager = wmService.mContext.getPackageManager();
final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
activityRecord.packageName);
@@ -49,8 +51,6 @@
mAppCompatOverrides, tmpController::shouldApplyUserFullscreenOverride,
tmpController::shouldApplyUserMinAspectRatioOverride,
tmpController::isSystemOverrideToFullscreenEnabled);
- mAppCompatCameraPolicy = new AppCompatCameraPolicy(activityRecord,
- mAppCompatOverrides.getAppCompatCameraOverrides());
}
@NonNull
@@ -64,11 +64,6 @@
}
@NonNull
- AppCompatCameraPolicy getAppCompatCameraPolicy() {
- return mAppCompatCameraPolicy;
- }
-
- @NonNull
AppCompatOverrides getAppCompatOverrides() {
return mAppCompatOverrides;
}
@@ -82,4 +77,12 @@
AppCompatCameraOverrides getAppCompatCameraOverrides() {
return mAppCompatOverrides.getAppCompatCameraOverrides();
}
+
+ @Nullable
+ AppCompatCameraPolicy getAppCompatCameraPolicy() {
+ if (mActivityRecord.mDisplayContent != null) {
+ return mActivityRecord.mDisplayContent.mAppCompatCameraPolicy;
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index b0fdbb5..155e246 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -22,7 +22,6 @@
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
-import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -31,8 +30,6 @@
import static com.android.server.wm.AppCompatUtils.asLazy;
import android.annotation.NonNull;
-import android.content.pm.ActivityInfo;
-import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.utils.OptPropFactory;
@@ -51,6 +48,8 @@
@NonNull
private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@NonNull
private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
@@ -62,8 +61,10 @@
AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord,
@NonNull LetterboxConfiguration letterboxConfiguration,
- @NonNull OptPropFactory optPropBuilder) {
+ @NonNull OptPropFactory optPropBuilder,
+ @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
mActivityRecord = activityRecord;
+ mAppCompatCameraOverrides = appCompatCameraOverrides;
mOrientationOverridesState = new OrientationOverridesState(mActivityRecord,
System::currentTimeMillis);
final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy(
@@ -76,59 +77,9 @@
isPolicyForIgnoringRequestedOrientationEnabled);
}
- /**
- * Whether should ignore app requested orientation in response to an app
- * calling {@link android.app.Activity#setRequestedOrientation}.
- *
- * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation}
- * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has
- * landscape natural orientation which app developers don't expect. For example, the loop can
- * look like this:
- * <ol>
- * <li>App sets default orientation to "unspecified" at runtime
- * <li>App requests to "portrait" after checking some condition (e.g. display rotation).
- * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because
- * app can't handle the corresponding config changes.
- * <li>Loop goes back to (1)
- * </ol>
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Flag gating the treatment is enabled
- * <li>Opt-out component property isn't enabled
- * <li>Opt-in component property or per-app override are enabled
- * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation}
- * call from an app or camera compat force rotation treatment is active for the activity.
- * <li>Orientation request loop detected and is not letterboxed for fixed orientation
- * </ul>
- */
- boolean shouldIgnoreRequestedOrientation(
- @ActivityInfo.ScreenOrientation int requestedOrientation) {
- if (mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION))) {
- if (mOrientationOverridesState.mIsRelaunchingAfterRequestedOrientationChanged) {
- Slog.w(TAG, "Ignoring orientation update to "
- + screenOrientationToString(requestedOrientation)
- + " due to relaunching after setRequestedOrientation for "
- + mActivityRecord);
- return true;
- }
- if (isCameraCompatTreatmentActive()) {
- Slog.w(TAG, "Ignoring orientation update to "
- + screenOrientationToString(requestedOrientation)
- + " due to camera compat treatment for " + mActivityRecord);
- return true;
- }
- }
-
- if (shouldIgnoreOrientationRequestLoop()) {
- Slog.w(TAG, "Ignoring orientation update to "
- + screenOrientationToString(requestedOrientation)
- + " as orientation request loop was detected for "
- + mActivityRecord);
- return true;
- }
- return false;
+ boolean shouldEnableIgnoreOrientationRequest() {
+ return mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty(
+ isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION));
}
/**
@@ -183,20 +134,6 @@
return mActivityRecord.info.isChangeEnabled(overrideChangeId);
}
- /**
- * @return {@code true} if the App Compat Camera Policy is active for the current activity.
- */
- // TODO(b/346253439): Remove after defining dependency with Camera capabilities.
- private boolean isCameraCompatTreatmentActive() {
- DisplayContent displayContent = mActivityRecord.mDisplayContent;
- if (displayContent == null) {
- return false;
- }
- return displayContent.mDisplayRotationCompatPolicy != null
- && displayContent.mDisplayRotationCompatPolicy
- .isTreatmentEnabledForActivity(mActivityRecord);
- }
-
static class OrientationOverridesState {
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
final boolean mIsOverrideToNosensorOrientationEnabled;
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 960ef5a..69ba59b 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -46,7 +46,6 @@
@NonNull
private final AppCompatOverrides mAppCompatOverrides;
-
@NonNull
private final BooleanSupplier mShouldApplyUserFullscreenOverride;
@NonNull
@@ -78,7 +77,7 @@
// often results in sideways or stretched previews. As the camera compat treatment
// targets fixed-orientation activities, overriding the orientation disables the
// treatment.
- && !mActivityRecord.isCameraActive()) {
+ && !mAppCompatOverrides.getAppCompatCameraOverrides().isCameraActive()) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -103,11 +102,11 @@
return candidate;
}
- if (displayContent != null && mAppCompatOverrides.getAppCompatCameraOverrides()
- .isOverrideOrientationOnlyForCameraEnabled()
- && (displayContent.mDisplayRotationCompatPolicy == null
- || !displayContent.mDisplayRotationCompatPolicy
- .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+ if (displayContent != null
+ && mAppCompatOverrides.getAppCompatCameraOverrides()
+ .isOverrideOrientationOnlyForCameraEnabled()
+ && !displayContent.mAppCompatCameraPolicy
+ .isActivityEligibleForOrientationOverride(mActivityRecord)) {
return candidate;
}
@@ -120,7 +119,7 @@
// often results in sideways or stretched previews. As the camera compat treatment
// targets fixed-orientation activities, overriding the orientation disables the
// treatment.
- && !mActivityRecord.isCameraActive()) {
+ && !mAppCompatOverrides.getAppCompatCameraOverrides().isCameraActive()) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER));
@@ -161,4 +160,62 @@
return candidate;
}
+ /**
+ * Whether should ignore app requested orientation in response to an app
+ * calling {@link android.app.Activity#setRequestedOrientation}.
+ *
+ * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation}
+ * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has
+ * landscape natural orientation which app developers don't expect. For example, the loop can
+ * look like this:
+ * <ol>
+ * <li>App sets default orientation to "unspecified" at runtime
+ * <li>App requests to "portrait" after checking some condition (e.g. display rotation).
+ * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because
+ * app can't handle the corresponding config changes.
+ * <li>Loop goes back to (1)
+ * </ol>
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Opt-out component property isn't enabled
+ * <li>Opt-in component property or per-app override are enabled
+ * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation}
+ * call from an app or camera compat force rotation treatment is active for the activity.
+ * <li>Orientation request loop detected and is not letterboxed for fixed orientation
+ * </ul>
+ */
+ boolean shouldIgnoreRequestedOrientation(
+ @ActivityInfo.ScreenOrientation int requestedOrientation) {
+ final AppCompatOrientationOverrides orientationOverrides =
+ mAppCompatOverrides.getAppCompatOrientationOverrides();
+ if (orientationOverrides.shouldEnableIgnoreOrientationRequest()) {
+ if (orientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to relaunching after setRequestedOrientation for "
+ + mActivityRecord);
+ return true;
+ }
+ final AppCompatCameraPolicy cameraPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatCameraPolicy();
+ if (cameraPolicy != null
+ && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to camera compat treatment for " + mActivityRecord);
+ return true;
+ }
+ }
+ if (orientationOverrides.shouldIgnoreOrientationRequestLoop()) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " as orientation request loop was detected for "
+ + mActivityRecord);
+ return true;
+ }
+ return false;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index c20da7c..94c6ba9 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -79,10 +79,10 @@
mLetterboxConfiguration = letterboxConfiguration;
mActivityRecord = activityRecord;
- mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
- mLetterboxConfiguration, optPropBuilder);
mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
mLetterboxConfiguration, optPropBuilder);
+ mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
+ mLetterboxConfiguration, optPropBuilder, mAppCompatCameraOverrides);
mFakeFocusOptProp = optPropBuilder.create(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS,
mLetterboxConfiguration::isCompatFakeFocusEnabled);
@@ -113,19 +113,6 @@
mLetterboxConfiguration::isUserAppAspectRatioFullscreenEnabled);
}
- /**
- * @return {@code true} if the App Compat Camera Policy is active for the current activity.
- */
- boolean isCameraCompatTreatmentActive() {
- final DisplayContent displayContent = mActivityRecord.mDisplayContent;
- if (displayContent == null) {
- return false;
- }
- return displayContent.mDisplayRotationCompatPolicy != null
- && displayContent.mDisplayRotationCompatPolicy
- .isTreatmentEnabledForActivity(mActivityRecord);
- }
-
@NonNull
AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
return mAppCompatOrientationOverrides;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 14e256f..8421765 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -210,6 +210,9 @@
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
+ // Clear the pointer down outside focus if any.
+ mWindowManagerService.clearPointerDownOutsideFocusRunnable();
+
// If we don't need to set up the animation, we return early. This is the case when
// - We have an application callback.
// - We don't have any ActivityRecord or Task to animate.
@@ -1361,6 +1364,8 @@
synchronized (openTask.mWmService.mGlobalLock) {
if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
mStartingSurface = sc;
+ } else {
+ sc.release();
}
}
}
@@ -1599,12 +1604,20 @@
@NonNull ActivityRecord[] visibleOpenActivities) {
boolean needsLaunchBehind = true;
if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
+ boolean activitiesAreDrawn = false;
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ // If the activity hasn't stopped, it's window should remain drawn.
+ activitiesAreDrawn |= visibleOpenActivities[i].firstWindowDrawn;
+ }
final WindowContainer mainOpen = openAnimationAdaptor.mAdaptors[0].mTarget;
final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
- openAnimationAdaptor.createStartingSurface(snapshot);
- // set LaunchBehind if we are creating splash screen surface.
- needsLaunchBehind = snapshot == null
- && openAnimationAdaptor.mRequestedStartingSurfaceId != INVALID_TASK_ID;
+ // Don't create starting surface if previous activities haven't stopped or
+ // the snapshot does not exist.
+ if (snapshot != null || !activitiesAreDrawn) {
+ openAnimationAdaptor.createStartingSurface(snapshot);
+ }
+ // Only use LaunchBehind if snapshot does not exist.
+ needsLaunchBehind = snapshot == null;
}
if (needsLaunchBehind) {
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 70e6d5d..efd5202 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,23 +22,14 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
-import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -47,14 +38,11 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
-import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
-import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -536,133 +524,22 @@
}
/**
- * @see ActivityRecord#applySizeOverrideIfNeeded
- */
- public static boolean applySizeOverride(DisplayContent displayContent, ApplicationInfo appInfo,
- Configuration newParentConfiguration, Configuration inOutConfig,
- boolean optOutEdgeToEdge, boolean hasFixedRotationTransform,
- boolean hasCompatDisplayInsets) {
- if (displayContent == null) {
- return false;
- }
- final boolean useOverrideInsetsForConfig =
- displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
- ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
- && !appInfo.isChangeEnabled(
- OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
- : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
- if (newParentConfiguration == null) {
- newParentConfiguration = displayContent.getConfiguration();
- }
- final int parentWindowingMode =
- newParentConfiguration.windowConfiguration.getWindowingMode();
- final boolean isFloating = isFloating(parentWindowingMode)
- // Check the requested windowing mode of activity as well in case it is
- // switching between PiP and fullscreen.
- && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
- || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
- rotation = displayContent.getRotation();
- }
- if (!optOutEdgeToEdge && (!useOverrideInsetsForConfig
- || hasCompatDisplayInsets
- || isFloating
- || rotation == ROTATION_UNDEFINED)) {
- // If the insets configuration decoupled logic is not enabled for the app, or the app
- // already has a compat override, or the context doesn't contain enough info to
- // calculate the override, skip the override.
- return false;
- }
- // Make sure the orientation related fields will be updated by the override insets, because
- // fixed rotation has assigned the fields from display's configuration.
- if (hasFixedRotationTransform) {
- inOutConfig.windowConfiguration.setAppBounds(null);
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.orientation = ORIENTATION_UNDEFINED;
- }
-
- // Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated
- ? displayContent.mBaseDisplayHeight
- : displayContent.mBaseDisplayWidth;
- final int dh = rotated
- ? displayContent.mBaseDisplayWidth
- : displayContent.mBaseDisplayHeight;
- final Rect nonDecorFrame = displayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorFrame;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.intersect(nonDecorFrame);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- }
- if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
- displayContent.computeSizeRanges(info, rotated, dw, dh,
- displayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
- return true;
- }
-
- /**
- * Gives the derived class a chance to apply the app-specific configuration.
- *
- * @param inOutConfig the configuration as the requested configuration.
- * @return true if any of the given configuration has been updated.
- */
- public boolean onApplyAppSpecificConfig(Configuration inOutConfig) {
- return false;
- }
-
- /**
* Applies app-specific nightMode and {@link LocaleList} on requested configuration.
* @return true if any of the requested configuration has been updated.
*/
public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
@Configuration.GrammaticalGender Integer gender) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
- boolean changed = onApplyAppSpecificConfig(mRequestsTmpConfig);
boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
nightMode);
boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
locales);
boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
- if (changed || newNightModeSet || newLocalesSet || newGenderSet) {
+ if (newNightModeSet || newLocalesSet || newGenderSet) {
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
- return changed || newNightModeSet || newLocalesSet || newGenderSet;
+ return newNightModeSet || newLocalesSet || newGenderSet;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b5b9377..a8aa0ba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -263,7 +263,6 @@
import com.android.server.wm.utils.RegionUtils;
import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -475,14 +474,8 @@
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
- @Nullable
- final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
- @Nullable
- final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
- @Nullable
- final CameraStateMonitor mCameraStateMonitor;
- @Nullable
- final ActivityRefresher mActivityRefresher;
+ @NonNull
+ AppCompatCameraPolicy mAppCompatCameraPolicy;
DisplayFrames mDisplayFrames;
final DisplayUpdater mDisplayUpdater;
@@ -1191,6 +1184,7 @@
mDeviceStateController = deviceStateController;
+ mAppCompatCameraPolicy = new AppCompatCameraPolicy(mWmService, this);
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
mDeviceStateController, root.getDisplayRotationCoordinator());
@@ -1231,40 +1225,6 @@
onDisplayChanged(this);
updateDisplayAreaOrganizers();
- // Not checking DeviceConfig value here to allow enabling via DeviceConfig
- // without the need to restart the device.
- final boolean shouldCreateDisplayRotationCompatPolicy =
- mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
- final boolean shouldCreateCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
- && DesktopModeLaunchParamsModifier.canEnterDesktopMode(mWmService.mContext);
- if (shouldCreateDisplayRotationCompatPolicy || shouldCreateCameraCompatFreeformPolicy) {
- mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
- mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
- if (shouldCreateDisplayRotationCompatPolicy) {
- mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(this,
- mCameraStateMonitor, mActivityRefresher);
- mDisplayRotationCompatPolicy.start();
- } else {
- mDisplayRotationCompatPolicy = null;
- }
-
- if (shouldCreateCameraCompatFreeformPolicy) {
- mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(this,
- mCameraStateMonitor, mActivityRefresher);
- mCameraCompatFreeformPolicy.start();
- } else {
- mCameraCompatFreeformPolicy = null;
- }
-
- mCameraStateMonitor.startListeningToCameraState();
- } else {
- // These are to satisfy the `final` check.
- mCameraStateMonitor = null;
- mActivityRefresher = null;
- mDisplayRotationCompatPolicy = null;
- mCameraCompatFreeformPolicy = null;
- }
-
mRotationReversionController = new DisplayRotationReversionController(this);
mInputMonitor = new InputMonitor(mWmService, this);
@@ -1280,6 +1240,7 @@
R.bool.config_defaultInTouchMode);
mWmService.mInputManager.setInTouchMode(mInTouchMode, mWmService.MY_PID, mWmService.MY_UID,
/* hasPermission= */ true, mDisplayId);
+ mAppCompatCameraPolicy.start();
}
private void beginHoldScreenUpdate() {
@@ -1314,15 +1275,6 @@
}
}
- /**
- * @return The {@link DisplayRotationCompatPolicy} for this DisplayContent
- */
- // TODO(b/335387481) Allow access to DisplayRotationCompatPolicy only with getters
- @Nullable
- DisplayRotationCompatPolicy getDisplayRotationCompatPolicy() {
- return mDisplayRotationCompatPolicy;
- }
-
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
@@ -2889,12 +2841,10 @@
}
}
- if (mDisplayRotationCompatPolicy != null) {
- int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
- if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
- mLastOrientationSource = null;
- return compatOrientation;
- }
+ final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
+ if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ mLastOrientationSource = null;
+ return compatOrientation;
}
final int orientation = super.getOrientation();
@@ -3364,17 +3314,7 @@
getPendingTransaction().apply();
mWmService.mWindowPlacerLocked.requestTraversal();
- if (mDisplayRotationCompatPolicy != null) {
- mDisplayRotationCompatPolicy.dispose();
- }
-
- if (mCameraCompatFreeformPolicy != null) {
- mCameraCompatFreeformPolicy.dispose();
- }
-
- if (mCameraStateMonitor != null) {
- mCameraStateMonitor.dispose();
- }
+ mAppCompatCameraPolicy.dispose();
}
/** Returns true if a removal action is still being deferred. */
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f3ccc3b..c67928a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -2294,10 +2294,8 @@
mInHalfFoldTransition = false;
mDeviceState = DeviceStateController.DeviceState.UNKNOWN;
}
- mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
- ? null
- : dc.mDisplayRotationCompatPolicy
- .getSummaryForDisplayRotationHistoryRecord();
+ mDisplayRotationCompatPolicySummary = dc.mAppCompatCameraPolicy
+ .getSummaryForDisplayRotationHistoryRecord();
mRotationReversionSlots =
dr.mDisplayContent.getRotationReversionController().getSlotsCopy();
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3d71e95..9998e1a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -299,8 +299,7 @@
// Checking whether an activity in fullscreen rather than the task as this camera
// compat treatment doesn't cover activity embedding.
if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- cameraActivity.mAppCompatController
- .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
+ recomputeConfigurationForCameraCompatIfNeeded(cameraActivity);
mDisplayContent.updateOrientation();
return true;
}
@@ -367,8 +366,7 @@
|| topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
return true;
}
- topActivity.mAppCompatController
- .getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
+ recomputeConfigurationForCameraCompatIfNeeded(topActivity);
mDisplayContent.updateOrientation();
return true;
}
@@ -383,4 +381,12 @@
}
return mActivityRefresher.isActivityRefreshing(topActivity);
}
+
+ private void recomputeConfigurationForCameraCompatIfNeeded(
+ @NonNull ActivityRecord activityRecord) {
+ if (activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldRecomputeConfigurationForCameraCompat()) {
+ activityRecord.recomputeConfiguration();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
index f94b8c4..b955738 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
@@ -61,7 +61,7 @@
}
boolean isRotationReversionEnabled() {
- return mDisplayContent.mDisplayRotationCompatPolicy != null
+ return mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()
|| mDisplayContent.getDisplayRotation().mFoldController != null
|| mDisplayContent.getIgnoreOrientationRequest();
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 30f2d0d..6abef8b 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.View.DRAG_FLAG_GLOBAL;
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
@@ -217,6 +218,11 @@
mDragState.mToken = dragToken;
mDragState.mDisplayContent = displayContent;
mDragState.mData = data;
+ mDragState.mCallingTaskIdToHide = shouldMoveCallingTaskToBack(callingWin,
+ flags);
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Calling task to hide=" + mDragState.mCallingTaskIdToHide);
+ }
if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
final Display display = displayContent.getDisplay();
@@ -364,6 +370,23 @@
}
/**
+ * If the calling window's task should be hidden for the duration of the drag, this returns the
+ * task id of the task (or -1 otherwise).
+ */
+ private int shouldMoveCallingTaskToBack(WindowState callingWin, int flags) {
+ if ((flags & View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) == 0) {
+ // Not requested by the app
+ return -1;
+ }
+ final ActivityRecord callingActivity = callingWin.getActivityRecord();
+ if (callingActivity == null || callingActivity.getTask() == null) {
+ // Not an activity
+ return -1;
+ }
+ return callingActivity.getTask().mTaskId;
+ }
+
+ /**
* Notifies the unhandled drag listener if needed.
* @return whether the listener was notified and subsequent drag completion should be deferred
* until the listener calls back
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 4be5bad..ba74f50 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
@@ -48,6 +49,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -117,6 +119,8 @@
InputInterceptor mInputInterceptor;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
+ // Set to non -1 value if a valid app requests DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START
+ int mCallingTaskIdToHide;
/**
* Whether if animation is completed. Needs to be volatile to update from the animation thread
* without having a WM lock.
@@ -320,12 +324,12 @@
}
}
final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
/* includeDragSurface= */ targetInterceptsGlobalDrag,
/* includeDragFlags= */ targetInterceptsGlobalDrag,
dragAndDropPermissions);
} else {
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+ return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
/* includeDragSurface= */ includePrivateInfo,
/* includeDragFlags= */ includePrivateInfo,
null /* dragAndDropPermissions */);
@@ -527,11 +531,24 @@
Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin);
}
// Only allow the extras to be dispatched to a global-intercepting drag target
- ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
+ ClipData data = null;
+ if (interceptsGlobalDrag) {
+ data = mData.copyForTransferWithActivityInfo();
+ PersistableBundle extras = data.getDescription().getExtras() != null
+ ? data.getDescription().getExtras()
+ : new PersistableBundle();
+ extras.putInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, mCallingTaskIdToHide);
+ // Note that setting extras always copies the bundle
+ data.getDescription().setExtras(extras);
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Adding EXTRA_HIDE_DRAG_SOURCE_TASK_ID=" + mCallingTaskIdToHide);
+ }
+ }
+ ClipDescription description = data != null ? data.getDescription() : mDataDescription;
DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
- data, false /* includeDragSurface */, true /* includeDragFlags */,
- null /* dragAndDropPermission */);
+ description, data, false /* includeDragSurface */,
+ true /* includeDragFlags */, null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
@@ -700,37 +717,51 @@
return mDragInProgress;
}
- private DragEvent obtainDragEvent(int action, float x, float y, ClipData data,
- boolean includeDragSurface, boolean includeDragFlags,
+ private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
+ ClipData data, boolean includeDragSurface, boolean includeDragFlags,
IDragAndDropPermissions dragAndDropPermissions) {
return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
includeDragFlags ? mFlags : 0,
- null /* localState */, mDataDescription, data,
+ null /* localState */, description, data,
includeDragSurface ? mSurfaceControl : null,
dragAndDropPermissions, false /* result */);
}
private ValueAnimator createReturnAnimationLocked() {
- final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
- mOriginalX - mThumbOffsetX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
- mOriginalY - mThumbOffsetY),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
- mAnimatedScale),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
+ final ValueAnimator animator;
+ final long duration;
+ if (mCallingTaskIdToHide != -1) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ duration = MIN_ANIMATION_DURATION_MS;
+ } else {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+ mOriginalX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+ mOriginalY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
- final float translateX = mOriginalX - mCurrentX;
- final float translateY = mOriginalY - mCurrentY;
- // Adjust the duration to the travel distance.
- final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
- final double displayDiagonal =
- Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
- final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
- * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+ final float translateX = mOriginalX - mCurrentX;
+ final float translateY = mOriginalY - mCurrentY;
+ // Adjust the duration to the travel distance.
+ final double travelDistance = Math.sqrt(
+ translateX * translateX + translateY * translateY);
+ final double displayDiagonal =
+ Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
+ duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
+ * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+ }
+
final AnimationListener listener = new AnimationListener();
animator.setDuration(duration);
animator.setInterpolator(mCubicEaseOutInterpolator);
@@ -742,13 +773,24 @@
}
private ValueAnimator createCancelAnimationLocked() {
- final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ final ValueAnimator animator;
+ if (mCallingTaskIdToHide != -1) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ } else {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+ PropertyValuesHolder.ofFloat(
+ ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ }
+
final AnimationListener listener = new AnimationListener();
animator.setDuration(MIN_ANIMATION_DURATION_MS);
animator.setInterpolator(mCubicEaseOutInterpolator);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index e924fb6..a3550bc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -462,12 +462,16 @@
final boolean isTabletopMode = isDisplayFullScreenAndInPosture(/* isTabletop */ true);
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
-
+ final AppCompatCameraOverrides appCompatCameraOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+ final AppCompatCameraPolicy cameraPolicy =
+ mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
+ final boolean isCameraCompatTreatmentActive = cameraPolicy != null
+ && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord);
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
- || mActivityRecord.mAppCompatController.getAppCompatCameraOverrides()
- .isCameraCompatSplitScreenAspectRatioAllowed()
- && getAppCompatOverrides().isCameraCompatTreatmentActive();
+ || (appCompatCameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
+ && isCameraCompatTreatmentActive);
}
private float getDefaultMinAspectRatioForUnresizableApps() {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index b452131..8c7b637 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -442,7 +442,10 @@
return taskDisplayArea;
}
- private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
+ /**
+ * Returns whether the given UID caller is the assistant.
+ */
+ public static boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
if (atmService.mActiveVoiceInteractionServiceComponent == null) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 3eb3218..31fda77 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -815,10 +815,8 @@
if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
// It also invokes kill().
mDisplayContent.setRotationAnimation(null);
- if (mDisplayContent.mDisplayRotationCompatPolicy != null) {
- mDisplayContent.mDisplayRotationCompatPolicy
- .onScreenRotationAnimationFinished();
- }
+ mDisplayContent.mAppCompatCameraPolicy
+ .onScreenRotationAnimationFinished();
} else {
kill();
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 310516b..75e3e65 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -349,7 +349,7 @@
final int callingPid = Binder.getCallingPid();
// Validate and resolve ClipDescription data before clearing the calling identity
validateAndResolveDragMimeTypeExtras(data, callingUid, callingPid, mPackageName);
- validateDragFlags(flags);
+ validateDragFlags(flags, callingUid);
final long ident = Binder.clearCallingIdentity();
try {
return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
@@ -375,12 +375,17 @@
* Validates the given drag flags.
*/
@VisibleForTesting
- void validateDragFlags(int flags) {
+ void validateDragFlags(int flags, int callingUid) {
if ((flags & View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) {
if (!mCanStartTasksFromRecents) {
throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
}
}
+ if ((flags & View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) {
+ if (!SafeActivityOptions.isAssistant(mService.mAtmService, callingUid)) {
+ throw new SecurityException("Caller is not the assistant");
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f839ed6..47af6fc 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -105,8 +105,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -1443,11 +1443,11 @@
asyncRotationController.onTransitionFinished();
}
dc.onTransitionFinished();
- if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
+ if (hasParticipatedDisplay) {
final ChangeInfo changeInfo = mChanges.get(dc);
if (changeInfo != null
&& changeInfo.mRotation != dc.getWindowConfiguration().getRotation()) {
- dc.mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+ dc.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
}
}
if (mTransientLaunches != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index edd118d..1d02f1c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -179,7 +179,7 @@
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
- protected final WindowList<E> mChildren = new WindowList<E>();
+ protected final ArrayList<E> mChildren = new ArrayList<E>();
// The specified orientation for this window container.
// Shouldn't be accessed directly since subclasses can override getOverrideOrientation.
@@ -855,7 +855,7 @@
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
while (!mChildren.isEmpty()) {
- final E child = mChildren.peekLast();
+ final E child = mChildren.getLast();
child.removeImmediately();
// Need to do this after calling remove on the child because the child might try to
// remove/detach itself from its parent which will cause an exception if we remove
@@ -979,7 +979,7 @@
switch (position) {
case POSITION_TOP:
- if (mChildren.peekLast() != child) {
+ if (getTopChild() != child) {
mChildren.remove(child);
mChildren.add(child);
onChildPositionChanged(child);
@@ -990,7 +990,7 @@
}
break;
case POSITION_BOTTOM:
- if (mChildren.peekFirst() != child) {
+ if (getBottomChild() != child) {
mChildren.remove(child);
mChildren.addFirst(child);
onChildPositionChanged(child);
@@ -1445,7 +1445,13 @@
/** Returns the top child container. */
E getTopChild() {
- return mChildren.peekLast();
+ final int n = mChildren.size();
+ return n == 0 ? null : mChildren.get(n - 1);
+ }
+
+ E getBottomChild() {
+ final int n = mChildren.size();
+ return n == 0 ? null : mChildren.get(0);
}
/**
@@ -2550,7 +2556,7 @@
}
if (mParent != null && mParent == other.mParent) {
- final WindowList<WindowContainer> list = mParent.mChildren;
+ final ArrayList<WindowContainer> list = mParent.mChildren;
return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
}
@@ -2587,7 +2593,7 @@
// The position of the first non-common ancestor in the common ancestor list determines
// which is greater the which.
- final WindowList<WindowContainer> list = commonAncestor.mChildren;
+ final ArrayList<WindowContainer> list = commonAncestor.mChildren;
return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
? 1 : -1;
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowList.java b/services/core/java/com/android/server/wm/WindowList.java
deleted file mode 100644
index 1e888f5..0000000
--- a/services/core/java/com/android/server/wm/WindowList.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import java.util.ArrayList;
-
-/**
- * An {@link ArrayList} with extended functionality to be used as the children data structure in
- * {@link WindowContainer}.
- */
-class WindowList<E> extends ArrayList<E> {
-
- public void addFirst(E e) {
- add(0, e);
- }
-
- E peekLast() {
- return size() > 0 ? get(size() - 1) : null;
- }
-
- E peekFirst() {
- return size() > 0 ? get(0) : null;
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0c1ec504..9a5f84c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -732,6 +732,13 @@
final DisplayWindowListenerController mDisplayNotificationController;
final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
+ /** Amount of time (in milliseconds) to delay the pointer down outside focus handling */
+ private static final int POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS = 50;
+
+ /** A runnable to handle pointer down outside focus event. */
+ @Nullable
+ private Runnable mPointerDownOutsideFocusRunnable;
+
boolean mDisplayFrozen = false;
long mDisplayFreezeTime = 0;
int mLastDisplayFreezeDuration = 0;
@@ -5896,7 +5903,8 @@
case ON_POINTER_DOWN_OUTSIDE_FOCUS: {
synchronized (mGlobalLock) {
final IBinder touchedToken = (IBinder) msg.obj;
- onPointerDownOutsideFocusLocked(getInputTargetFromToken(touchedToken));
+ onPointerDownOutsideFocusLocked(getInputTargetFromToken(touchedToken),
+ true /* fromHandler */);
}
break;
}
@@ -7922,7 +7930,8 @@
synchronized (mGlobalLock) {
final InputTarget inputTarget =
WindowManagerService.this.getInputTargetFromWindowTokenLocked(windowToken);
- WindowManagerService.this.onPointerDownOutsideFocusLocked(inputTarget);
+ WindowManagerService.this.onPointerDownOutsideFocusLocked(inputTarget,
+ false /* fromHandler */);
}
}
@@ -8997,39 +9006,78 @@
}
}
- private void onPointerDownOutsideFocusLocked(InputTarget t) {
+ void clearPointerDownOutsideFocusRunnable() {
+ if (mPointerDownOutsideFocusRunnable == null) return;
+
+ mH.removeCallbacks(mPointerDownOutsideFocusRunnable);
+ mPointerDownOutsideFocusRunnable = null;
+ }
+
+ private void onPointerDownOutsideFocusLocked(InputTarget t, boolean fromHandler) {
if (t == null || !t.receiveFocusFromTapOutside()) {
// If the window that received the input event cannot receive keys, don't move the
// display it's on to the top since that window won't be able to get focus anyway.
return;
}
- if (mRecentsAnimationController != null
- && mRecentsAnimationController.getTargetAppMainWindow() == t) {
- // If there is an active recents animation and touched window is the target, then ignore
- // the touch. The target already handles touches using its own input monitor and we
- // don't want to trigger any lifecycle changes from focusing another window.
- // TODO(b/186770026): We should remove this once we support multiple resumed activities
- // while in overview
- return;
- }
+ clearPointerDownOutsideFocusRunnable();
+
+ // For embedded activity that is showing side-by-side with another activity, delay
+ // handling the touch-outside event to prevent focus rapid changes back-n-forth.
+ // Otherwise, handle the touch-outside event directly.
final WindowState w = t.getWindowState();
- if (w != null) {
- final Task task = w.getTask();
- if (task != null && w.mTransitionController.isTransientHide(task)) {
- // Don't disturb transient animation by accident touch.
+ final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
+ if (activity != null && activity.isEmbedded()
+ && activity.getTaskFragment().getAdjacentTaskFragment() != null) {
+ mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
+ } else if (!fromHandler) {
+ // Still post the runnable to handler thread in case there is already a runnable
+ // in execution, but still waiting to hold the wm lock.
+ mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mH.post(mPointerDownOutsideFocusRunnable);
+ } else {
+ handlePointerDownOutsideFocus(t);
+ }
+ }
+
+ private void handlePointerDownOutsideFocus(InputTarget t) {
+ synchronized (mGlobalLock) {
+ if (mPointerDownOutsideFocusRunnable != null
+ && mH.hasCallbacks(mPointerDownOutsideFocusRunnable)) {
+ // Skip if there's another pending pointer-down-outside-focus event.
return;
}
- }
+ clearPointerDownOutsideFocusRunnable();
- ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s",
- t);
- if (mFocusedInputTarget != t && mFocusedInputTarget != null) {
- mFocusedInputTarget.handleTapOutsideFocusOutsideSelf();
+ if (mRecentsAnimationController != null
+ && mRecentsAnimationController.getTargetAppMainWindow() == t) {
+ // If there is an active recents animation and touched window is the target,
+ // then ignore the touch. The target already handles touches using its own
+ // input monitor and we don't want to trigger any lifecycle changes from
+ // focusing another window.
+ // TODO(b/186770026): We should remove this once we support multiple resumed
+ // activities while in overview
+ return;
+ }
+
+ final WindowState w = t.getWindowState();
+ if (w != null) {
+ final Task task = w.getTask();
+ if (task != null && w.mTransitionController.isTransientHide(task)) {
+ // Don't disturb transient animation by accident touch.
+ return;
+ }
+ }
+
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s", t);
+ if (mFocusedInputTarget != t && mFocusedInputTarget != null) {
+ mFocusedInputTarget.handleTapOutsideFocusOutsideSelf();
+ }
+ // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities.
+ mAtmService.mTaskSupervisor.mUserLeaving = true;
+ t.handleTapOutsideFocusInsideSelf();
+ mAtmService.mTaskSupervisor.mUserLeaving = false;
}
- // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities.
- mAtmService.mTaskSupervisor.mUserLeaving = true;
- t.handleTapOutsideFocusInsideSelf();
- mAtmService.mTaskSupervisor.mUserLeaving = false;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index deb7098..e900488 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1596,7 +1596,7 @@
case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
- if (task.mChildren.peekFirst() != taskFragment) {
+ if (task.getBottomChild() != taskFragment) {
task.mChildren.remove(taskFragment);
task.mChildren.add(0, taskFragment);
if (!taskFragment.hasChild()) {
@@ -1612,7 +1612,7 @@
case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
final Task task = taskFragment.getTask();
if (task != null) {
- if (task.mChildren.peekLast() != taskFragment) {
+ if (task.getTopChild() != taskFragment) {
task.mChildren.remove(taskFragment);
task.mChildren.add(taskFragment);
if (!taskFragment.hasChild()) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index f6942ee..60d3e78 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1581,25 +1581,6 @@
}
@Override
- public boolean onApplyAppSpecificConfig(Configuration inOutConfig) {
- if (mConfigActivityRecord != null) {
- // Let the activity decide whether to apply the size override.
- return false;
- }
- final DisplayContent displayContent = mAtm.mWindowManager != null
- ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
- : null;
- return applySizeOverride(
- displayContent,
- mInfo,
- null /* newParentConfiguration */,
- inOutConfig,
- false /* optOutEdgeToEdge */,
- false /* hasFixedRotationTransform */,
- false /* hasCompatDisplayInsets */);
- }
-
- @Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
super.onConfigurationChanged(newGlobalConfig);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7649a4e..3cd5f76 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -210,6 +210,7 @@
"android.system.suspend-V1-ndk",
"server_configurable_flags",
"service.incremental",
+ "android.companion.virtualdevice.flags-aconfig-cc",
],
static_libs: [
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 5c4db24..a32b0f1 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -19,6 +19,7 @@
#include <android-base/unique_fd.h>
#include <android/input.h>
#include <android/keycodes.h>
+#include <android_companion_virtualdevice_flags.h>
#include <errno.h>
#include <fcntl.h>
#include <input/Input.h>
@@ -37,6 +38,8 @@
namespace android {
+namespace vd_flags = android::companion::virtualdevice::flags;
+
static constexpr jlong INVALID_PTR = 0;
enum class DeviceType {
@@ -88,6 +91,10 @@
ioctl(fd, UI_SET_RELBIT, REL_Y);
ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
ioctl(fd, UI_SET_RELBIT, REL_HWHEEL);
+ if (vd_flags::high_resolution_scroll()) {
+ ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
+ ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES);
+ }
break;
case DeviceType::TOUCHSCREEN:
ioctl(fd, UI_SET_EVBIT, EV_ABS);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 791d030..e5a1ebf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2445,11 +2445,11 @@
t.traceEnd();
}
- t.traceBegin("CertBlacklister");
+ t.traceBegin("CertBlocklister");
try {
- CertBlacklister blacklister = new CertBlacklister(context);
+ CertBlocklister blocklister = new CertBlocklister(context);
} catch (Throwable e) {
- reportWtf("starting CertBlacklister", e);
+ reportWtf("starting CertBlocklister", e);
}
t.traceEnd();
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index c76bcf8..3ed6ad7 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -404,7 +404,7 @@
String traceTag = traceInitialization ? "camera_init" : "camera";
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- mIProfcollect.trace_system(traceTag);
+ mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider");
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 7aec42b..00daf41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -43,6 +43,7 @@
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
@@ -142,6 +143,7 @@
private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>();
private @Mock ActivityManager mActivityManager;
+ private @Mock ActivityManagerInternal mActivityManagerInternal;
private @Mock AlarmManager mAlarmManager;
private @Mock BiometricManager mBiometricManager;
private @Mock DevicePolicyManager mDevicePolicyManager;
@@ -158,6 +160,7 @@
private HandlerThread mHandlerThread;
private TrustManagerService mService;
private ITrustManager mTrustManager;
+ private ActivityManagerInternal mPreviousActivityManagerInternal;
@Before
public void setUp() throws Exception {
@@ -210,6 +213,11 @@
mMockContext.setMockPackageManager(mPackageManager);
mMockContext.addMockSystemService(UserManager.class, mUserManager);
doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+ mPreviousActivityManagerInternal = LocalServices.getService(
+ ActivityManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class,
+ mActivityManagerInternal);
LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
@@ -257,7 +265,14 @@
@After
public void tearDown() {
LocalServices.removeServiceForTest(SystemServiceManager.class);
- mHandlerThread.quit();
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ if (mPreviousActivityManagerInternal != null) {
+ LocalServices.addService(ActivityManagerInternal.class,
+ mPreviousActivityManagerInternal);
+ }
+ if (mHandlerThread != null) {
+ mHandlerThread.quit();
+ }
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index d151345..559c324 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -188,6 +188,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
@SmallTest
@@ -489,6 +490,34 @@
when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
}
+ private static void testThreadSafety(Runnable operationToTest, int nThreads,
+ int nRunsPerThread) throws Exception {
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch doneLatch = new CountDownLatch(nThreads);
+
+ for (int i = 0; i < nThreads; i++) {
+ Runnable threadRunnable = () -> {
+ try {
+ startLatch.await();
+ for (int j = 0; j < nRunsPerThread; j++) {
+ operationToTest.run();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ doneLatch.countDown();
+ }
+ };
+ new Thread(threadRunnable, "Test Thread #" + i).start();
+ }
+
+ // Ready set go
+ startLatch.countDown();
+
+ // Wait for all test threads to be done.
+ doneLatch.await();
+ }
+
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
@@ -6193,6 +6222,36 @@
.isEqualTo(IMPORTANCE_LOW);
}
+
+ @Test
+ public void testRestoredWithoutUid_threadSafety() throws Exception {
+ when(mPm.getPackageUidAsUser(anyString(), anyInt())).thenReturn(UNKNOWN_UID);
+ when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+ when(mClock.millis()).thenReturn(System.currentTimeMillis());
+ testThreadSafety(() -> {
+ String id = "id";
+ String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + Thread.currentThread()+ "\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "<package name=\"" + PKG_P + "\" show_badge=\"true\">\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ try {
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ // trigger a removal from the list
+ mXmlHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_P},
+ new int[]{UNKNOWN_UID});
+ }, 20, 50);
+ }
+
private static NotificationChannel cloneChannel(NotificationChannel original) {
Parcel parcel = Parcel.obtain();
try {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ef3df6c..eb8825c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -116,7 +116,6 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
@@ -2482,10 +2481,10 @@
assertTrue(activity.mChildren.contains(win4));
// The starting window should be on-top of all other windows.
- assertEquals(startingWin, activity.mChildren.peekLast());
+ assertEquals(startingWin, activity.getTopChild());
// The base application window should be below all other windows.
- assertEquals(baseWin, activity.mChildren.peekFirst());
+ assertEquals(baseWin, activity.getBottomChild());
activity.removeImmediately();
}
@@ -3508,23 +3507,6 @@
}
@Test
- public void testIsCameraActive() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final DisplayRotationCompatPolicy displayRotationCompatPolicy = mock(
- DisplayRotationCompatPolicy.class);
- when(mDisplayContent.getDisplayRotationCompatPolicy()).thenReturn(
- displayRotationCompatPolicy);
-
- when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
- anyBoolean())).thenReturn(false);
- assertFalse(app.mActivityRecord.isCameraActive());
-
- when(displayRotationCompatPolicy.isCameraActive(any(ActivityRecord.class),
- anyBoolean())).thenReturn(true);
- assertTrue(app.mActivityRecord.isCameraActive());
- }
-
- @Test
public void testUpdateCameraCompatStateFromUser_clickedOnDismiss() throws RemoteException {
final ActivityRecord activity = createActivityWithTask();
// Mock a flag being enabled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 467050e..f79cdc1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -69,6 +69,7 @@
private final int mDisplayWidth;
private final int mDisplayHeight;
+ private DisplayContent mDisplayContent;
AppCompatActivityRobot(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
@@ -79,6 +80,7 @@
mDisplayHeight = displayHeight;
mActivityStack = new TestComponentStack<>();
mTaskStack = new TestComponentStack<>();
+ createNewDisplay();
}
AppCompatActivityRobot(@NonNull WindowManagerService wm,
@@ -87,13 +89,19 @@
}
void createActivityWithComponent() {
- createActivityWithComponentInNewTask(/* inNewTask */ mTaskStack.isEmpty());
+ createActivityWithComponentInNewTask(/* inNewTask */ mTaskStack.isEmpty(),
+ /* inNewDisplay */ false);
}
void createActivityWithComponentInNewTask() {
- createActivityWithComponentInNewTask(/* inNewTask */ true);
+ createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ false);
}
+ void createActivityWithComponentInNewTaskAndDisplay() {
+ createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ true);
+ }
+
+
void configureTopActivity(float minAspect, float maxAspect, int screenOrientation,
boolean isUnresizable) {
prepareLimitedBounds(mActivityStack.top(), minAspect, maxAspect, screenOrientation,
@@ -110,12 +118,22 @@
/* isUnresizable */ true);
}
+ void activateCameraInPolicy(boolean isCameraActive) {
+ doReturn(isCameraActive).when(mDisplayContent.mAppCompatCameraPolicy)
+ .isCameraActive(any(ActivityRecord.class), anyBoolean());
+ }
+
@NonNull
ActivityRecord top() {
return mActivityStack.top();
}
@NonNull
+ DisplayContent displayContent() {
+ return mDisplayContent;
+ }
+
+ @NonNull
ActivityRecord getFromTop(int fromTop) {
return mActivityStack.getFromTop(fromTop);
}
@@ -130,7 +148,7 @@
}
void enableTreatmentForTopActivity(boolean enabled) {
- doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
+ doReturn(enabled).when(mDisplayContent.mAppCompatCameraPolicy)
.isTreatmentEnabledForActivity(eq(mActivityStack.top()));
}
@@ -164,7 +182,7 @@
}
void setIgnoreOrientationRequest(boolean enabled) {
- mActivityStack.top().mDisplayContent.setIgnoreOrientationRequest(enabled);
+ mDisplayContent.setIgnoreOrientationRequest(enabled);
}
void setTopActivityAsEmbedded(boolean embedded) {
@@ -179,20 +197,22 @@
mActivityStack.applyTo(/* fromTop */ fromTop, ActivityRecord::removeImmediately);
}
+ void createNewDisplay() {
+ mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
+ .build();
+ spyOnAppCompatCameraPolicy();
+ }
+
void createNewTask() {
- final DisplayContent displayContent = new TestDisplayContent
- .Builder(mAtm, mDisplayWidth, mDisplayHeight).build();
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
- .setDisplay(displayContent).build();
+ .setDisplay(mDisplayContent).build();
mTaskStack.push(newTask);
}
void createNewTaskWithBaseActivity() {
- final DisplayContent displayContent = new TestDisplayContent
- .Builder(mAtm, mDisplayWidth, mDisplayHeight).build();
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setDisplay(displayContent).build();
+ .setDisplay(mDisplayContent).build();
mTaskStack.push(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
}
@@ -319,7 +339,10 @@
pushActivity(newActivity);
}
- private void createActivityWithComponentInNewTask(boolean inNewTask) {
+ private void createActivityWithComponentInNewTask(boolean inNewTask, boolean inNewDisplay) {
+ if (inNewDisplay) {
+ createNewDisplay();
+ }
if (inNewTask) {
createNewTask();
}
@@ -369,7 +392,8 @@
}
private DisplayRotationCompatPolicy getTopDisplayRotationCompatPolicy() {
- return mActivityStack.top().mDisplayContent.mDisplayRotationCompatPolicy;
+ return mActivityStack.top().mDisplayContent
+ .mAppCompatCameraPolicy.mDisplayRotationCompatPolicy;
}
// We add the activity to the stack and spyOn() on its properties.
@@ -377,10 +401,16 @@
mActivityStack.push(activity);
spyOn(activity);
spyOn(activity.mAppCompatController.getTransparentPolicy());
- if (activity.mDisplayContent != null
- && activity.mDisplayContent.mDisplayRotationCompatPolicy != null) {
- spyOn(activity.mDisplayContent.mDisplayRotationCompatPolicy);
- }
spyOn(activity.mLetterboxUiController);
}
+
+ private void spyOnAppCompatCameraPolicy() {
+ spyOn(mDisplayContent.mAppCompatCameraPolicy);
+ if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ spyOn(mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+ }
+ if (mDisplayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(mDisplayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 9263b4f..2d94b34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -26,7 +26,6 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
@@ -264,14 +263,29 @@
public void testShouldRecomputeConfigurationForCameraCompat() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
- robot.activity().createActivityWithComponentInNewTask();
- robot.activateCamera(true);
- robot.activity().setShouldCreateCompatDisplayInsets(false);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.activateCameraInPolicy(true);
+ a.setShouldCreateCompatDisplayInsets(false);
+ });
robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
});
}
+ @Test
+ public void testIsCameraActive() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.activateCameraInPolicy(/* isCameraActive */ false);
+ robot.checkIsCameraActive(/* active */ false);
+ a.activateCameraInPolicy(/* isCameraActive */ true);
+ robot.checkIsCameraActive(/* active */ true);
+ });
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -289,10 +303,6 @@
super(wm, atm, supervisor);
}
- void activateCamera(boolean isCameraActive) {
- doReturn(isCameraActive).when(activity().top()).isCameraActive();
- }
-
void checkShouldRefreshActivityForCameraCompat(boolean expected) {
Assert.assertEquals(getAppCompatCameraOverrides()
.shouldRefreshActivityForCameraCompat(), expected);
@@ -313,6 +323,10 @@
.shouldApplyFreeformTreatmentForCameraCompat(), expected);
}
+ void checkIsCameraActive(boolean active) {
+ Assert.assertEquals(getAppCompatCameraOverrides().isCameraActive(), active);
+ }
+
private AppCompatCameraOverrides getAppCompatCameraOverrides() {
return activity().top().mAppCompatController.getAppCompatCameraOverrides();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 4116313..006b370 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -16,23 +16,20 @@
package com.android.server.wm;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
-import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static org.mockito.ArgumentMatchers.any;
import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
+import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -54,96 +51,128 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Test
- @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
- public void testRecomputeConfigurationForCameraCompatIfNeeded_allDisabledNoRecompute() {
+ public void testDisplayRotationCompatPolicy_presentWhenEnabled() {
runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
- robot.activateCamera(/* isCameraActive */ false);
-
- robot.recomputeConfigurationForCameraCompatIfNeeded();
- robot.checkRecomputeConfigurationInvoked(/* invoked */ false);
-
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(true);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasDisplayRotationCompatPolicy(true);
});
}
@Test
- @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
- public void testRecomputeConfigurationForCameraCompatIfNeeded_cameraEnabledRecompute() {
+ public void testDisplayRotationCompatPolicy_notPresentWhenDisabled() {
runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
- robot.activateCamera(/* isCameraActive */ false);
-
- robot.recomputeConfigurationForCameraCompatIfNeeded();
- robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ robot.conf().enableCameraCompatTreatmentAtBuildTime(false);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasDisplayRotationCompatPolicy(false);
});
}
@Test
- @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
- public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeeded_recompute() {
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() {
runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
- robot.activateCamera(/* isCameraActive */ false);
-
- robot.recomputeConfigurationForCameraCompatIfNeeded();
- robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ robot.allowEnterDesktopMode(true);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasCameraCompatFreeformPolicy(true);
});
}
@Test
- @DisableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
- @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void testRecomputeConfigurationForCameraSplitScreenCompatIfNeededWithCamera_recompute() {
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() {
runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.conf().enableCameraCompatSplitScreenAspectRatio(false);
- robot.activateCamera(/* isCameraActive */ true);
-
- robot.recomputeConfigurationForCameraCompatIfNeeded();
- robot.checkRecomputeConfigurationInvoked(/* invoked */ true);
+ robot.allowEnterDesktopMode(false);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
});
}
- void runTestScenario(@NonNull Consumer<CameraPolicyRobotTest> consumer) {
+ @Test
+ @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() {
+ runTestScenario((robot) -> {
+ robot.allowEnterDesktopMode(true);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
+ });
+ }
+
+ @Test
+ @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() {
+ runTestScenario((robot) -> {
+ robot.allowEnterDesktopMode(false);
+ robot.activity().createActivityWithComponentInNewTaskAndDisplay();
+ robot.checkTopActivityHasCameraCompatFreeformPolicy(false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<DisplayRotationPolicyRobotTest> consumer) {
spyOn(mWm.mLetterboxConfiguration);
- final CameraPolicyRobotTest robot = new CameraPolicyRobotTest(mWm, mAtm, mSupervisor);
+ final DisplayRotationPolicyRobotTest robot =
+ new DisplayRotationPolicyRobotTest(mWm, mAtm, mSupervisor);
consumer.accept(robot);
}
- private static class CameraPolicyRobotTest extends AppCompatRobotBase {
+ @Test
+ public void testIsCameraCompatTreatmentActive_whenTreatmentForTopActivityIsEnabled() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a)-> {
+ a.createActivityWithComponent();
+ a.enableTreatmentForTopActivity(/* enabled */ true);
+ });
- private final WindowManagerService mWm;
+ robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ true);
+ });
+ }
- CameraPolicyRobotTest(@NonNull WindowManagerService wm,
+ @Test
+ public void testIsCameraCompatTreatmentNotActive_whenTreatmentForTopActivityIsDisabled() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a)-> {
+ a.createActivityWithComponent();
+ a.enableTreatmentForTopActivity(/* enabled */ false);
+ });
+
+ robot.checkIsCameraCompatTreatmentActiveForTopActivity(/* active */ false);
+ });
+ }
+
+ private static class DisplayRotationPolicyRobotTest extends AppCompatRobotBase {
+
+ DisplayRotationPolicyRobotTest(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm,
@NonNull ActivityTaskSupervisor supervisor) {
super(wm, atm, supervisor);
- mWm = wm;
- spyOn(mWm);
}
- void activateCamera(boolean isCameraActive) {
- doReturn(isCameraActive).when(activity().top()).isCameraActive();
+ void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) {
+ Assert.assertEquals(exists, activity().top().mDisplayContent
+ .mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy());
}
- void recomputeConfigurationForCameraCompatIfNeeded() {
- getAppCompatCameraPolicy().recomputeConfigurationForCameraCompatIfNeeded();
+ void checkTopActivityHasCameraCompatFreeformPolicy(boolean exists) {
+ Assert.assertEquals(exists, activity().top().mDisplayContent
+ .mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
- void checkRecomputeConfigurationInvoked(boolean invoked) {
- if (invoked) {
- verify(activity().top()).recomputeConfiguration();
- } else {
- verify(activity().top(), never()).recomputeConfiguration();
- }
+ void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) {
+ Assert.assertEquals(getTopAppCompatCameraPolicy()
+ .isTreatmentEnabledForActivity(activity().top()), active);
}
- private AppCompatCameraPolicy getAppCompatCameraPolicy() {
- return activity().top().mAppCompatController.getAppCompatCameraPolicy();
+ // TODO(b/350460645): Create Desktop Windowing Robot to reuse common functionalities.
+ void allowEnterDesktopMode(boolean isAllowed) {
+ doReturn(isAllowed).when(() ->
+ DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
+ }
+
+ private AppCompatCameraPolicy getTopAppCompatCameraPolicy() {
+ return activity().top().mDisplayContent.mAppCompatCameraPolicy;
}
}
-
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 1720b64..35c2ee0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -15,12 +15,8 @@
*/
package com.android.server.wm;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
-import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.AppCompatOrientationOverrides.OrientationOverridesState.MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
@@ -31,7 +27,6 @@
import static org.junit.Assert.assertTrue;
import android.compat.testing.PlatformCompatChangeRule;
-import android.content.res.Configuration;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -62,82 +57,6 @@
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
- public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
- runTestScenario((robot) -> {
- robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
- robot.activity().createActivityWithComponent();
- robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
- public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
- runTestScenario((robot) -> {
- robot.applyOnConf((c) -> {
- c.enableCameraCompatTreatment(true);
- c.enableCameraCompatTreatmentAtBuildTime(true);
- c.enablePolicyForIgnoringRequestedOrientation(true);
- });
- robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
- a.enableTreatmentForTopActivity(true);
- });
- robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
- @Test
- public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() {
- runTestScenario((robot) -> {
- robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
-
- robot.activity().createActivityWithComponent();
- robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
- @Test
- public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() {
- runTestScenario((robot) -> {
- robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
- robot.prop().enable(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
-
- robot.activity().createActivityWithComponent();
- robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
- public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse()
- throws Exception {
- runTestScenario((robot) -> {
- robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
- robot.prop().disable(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
-
- robot.activity().createActivityWithComponent();
- robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
- @Test
public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() {
runTestScenario((robot) -> {
robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
@@ -239,21 +158,6 @@
});
}
- @Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
- public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
- runTestScenario((robot) -> {
- robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
- robot.applyOnActivity((a) -> {
- a.createActivityWithComponent();
- a.setLetterboxedForFixedOrientationAndAspectRatio(false);
- });
-
- robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
- /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
- });
- }
-
/**
* Runs a test scenario providing a Robot.
*/
@@ -276,10 +180,6 @@
mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierFake();
}
- void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) {
- getTopOrientationOverrides().setRelaunchingAfterRequestedOrientationChanged(enabled);
- }
-
// Useful to reduce timeout during tests
void prepareMockedTime() {
getTopOrientationOverrides().mOrientationOverridesState.mCurrentTimeMillisSupplier =
@@ -290,12 +190,6 @@
mTestCurrentTimeMillisSupplier.delay(SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS);
}
- void checkShouldIgnoreRequestedOrientation(boolean expected,
- @Configuration.Orientation int requestedOrientation) {
- assertEquals(expected, getTopOrientationOverrides()
- .shouldIgnoreRequestedOrientation(requestedOrientation));
- }
-
void checkExpectedLoopCount(int expectedCount) {
assertEquals(expectedCount, getTopOrientationOverrides()
.getSetOrientationRequestCounter());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index 9885a2d..aa520e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -18,6 +18,8 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -33,13 +35,16 @@
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -266,7 +271,7 @@
c.enableCameraCompatTreatmentAtBuildTime(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
+ a.createActivityWithComponentInNewTaskAndDisplay();
a.setTopActivityEligibleForOrientationOverride(false);
});
@@ -285,7 +290,7 @@
c.enableCameraCompatTreatmentAtBuildTime(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
+ a.createActivityWithComponentInNewTaskAndDisplay();
a.setTopActivityEligibleForOrientationOverride(true);
});
@@ -315,7 +320,7 @@
c.enableCameraCompatTreatmentAtBuildTime(true);
});
robot.applyOnActivity((a) -> {
- a.createActivityWithComponentInNewTask();
+ a.createActivityWithComponentInNewTaskAndDisplay();
a.setTopActivityCameraActive(false);
});
@@ -398,6 +403,97 @@
});
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
+ robot.activity().createActivityWithComponent();
+ robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.applyOnConf((c) -> {
+ c.enableCameraCompatTreatment(true);
+ c.enableCameraCompatTreatmentAtBuildTime(true);
+ c.enablePolicyForIgnoringRequestedOrientation(true);
+ });
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ a.enableTreatmentForTopActivity(true);
+ });
+ robot.prepareRelaunchingAfterRequestedOrientationChanged(false);
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
+
+ robot.activity().createActivityWithComponent();
+ robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
+ robot.prop().enable(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+
+ robot.activity().createActivityWithComponent();
+ robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ true,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse()
+ throws Exception {
+ runTestScenario((robot) -> {
+ robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
+ robot.prop().disable(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+
+ robot.activity().createActivityWithComponent();
+ robot.prepareRelaunchingAfterRequestedOrientationChanged(true);
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
+ public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.conf().enablePolicyForIgnoringRequestedOrientation(true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setLetterboxedForFixedOrientationAndAspectRatio(false);
+ });
+
+ robot.checkShouldIgnoreRequestedOrientation(/* expected */ false,
+ /* requestedOrientation */ SCREEN_ORIENTATION_UNSPECIFIED);
+ });
+ }
+
/**
* Runs a test scenario with an existing activity providing a Robot.
@@ -440,6 +536,10 @@
}
}
+ void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) {
+ getTopOrientationOverrides().setRelaunchingAfterRequestedOrientationChanged(enabled);
+ }
+
int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) {
return activity().top().mAppCompatController.getOrientationPolicy()
.overrideOrientationIfNeeded(candidate);
@@ -451,12 +551,27 @@
void checkOverrideOrientation(@ActivityInfo.ScreenOrientation int candidate,
@ActivityInfo.ScreenOrientation int expected) {
- Assert.assertEquals(expected, overrideOrientationIfNeeded(candidate));
+ assertEquals(expected, overrideOrientationIfNeeded(candidate));
}
void checkOverrideOrientationIsNot(@ActivityInfo.ScreenOrientation int candidate,
@ActivityInfo.ScreenOrientation int notExpected) {
Assert.assertNotEquals(notExpected, overrideOrientationIfNeeded(candidate));
}
+
+ void checkShouldIgnoreRequestedOrientation(boolean expected,
+ @Configuration.Orientation int requestedOrientation) {
+ assertEquals(expected, getTopAppCompatOrientationPolicy()
+ .shouldIgnoreRequestedOrientation(requestedOrientation));
+ }
+
+ private AppCompatOrientationOverrides getTopOrientationOverrides() {
+ return activity().top().mAppCompatController.getAppCompatOverrides()
+ .getAppCompatOrientationOverrides();
+ }
+
+ private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
+ return activity().top().mAppCompatController.getOrientationPolicy();
+ }
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6957502..5739a04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -117,8 +117,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.view.Display;
@@ -2832,7 +2832,7 @@
doReturn(true).when(() ->
DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
- assertNotNull(createNewDisplay().mCameraCompatFreeformPolicy);
+ assertTrue(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
@DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
@@ -2841,14 +2841,14 @@
doReturn(true).when(() ->
DesktopModeLaunchParamsModifier.canEnterDesktopMode(any()));
- assertNull(createNewDisplay().mCameraCompatFreeformPolicy);
+ assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
@EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
public void desktopWindowingFlagNotEnabled_cameraCompatFreeformPolicyIsNull() {
- assertNull(createNewDisplay().mCameraCompatFreeformPolicy);
+ assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
private void removeRootTaskTests(Runnable runnable) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index e3a8542..2e488d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1607,6 +1607,7 @@
.thenReturn(mMockDeviceStateManager);
mDeviceStateController = mock(DeviceStateController.class);
+ mMockDisplayContent.mAppCompatCameraPolicy = mock(AppCompatCameraPolicy.class);
mTarget = new TestDisplayRotation(mMockDisplayContent, mMockDisplayAddress,
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext,
mDeviceStateController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 7faf2aa..8cdb574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -497,7 +497,8 @@
public void testValidateFlags() {
final Session session = getTestSession();
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
+ 0 /* callingUid */);
fail("Expected failure without permission");
} catch (SecurityException e) {
// Expected failure
@@ -510,7 +511,8 @@
.checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
final Session session = createTestSession(mAtm);
try {
- session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
+ session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION,
+ 0 /* callingUid */);
// Expected pass
} catch (SecurityException e) {
fail("Expected no failure with permission");
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 74e2d44..51b3c48 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -636,8 +636,9 @@
@Test
@EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
- doReturn(true).when(mActivity).isCameraActive();
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -647,9 +648,10 @@
@EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue()
throws Exception {
- doReturn(true).when(mActivity).isCameraActive();
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -659,9 +661,10 @@
@EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse()
throws Exception {
- doReturn(false).when(mActivity).isCameraActive();
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
+ doReturn(false).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -671,9 +674,10 @@
@DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse()
throws Exception {
- doReturn(true).when(mActivity).isCameraActive();
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -682,8 +686,9 @@
@Test
@DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() {
- doReturn(true).when(mActivity).isCameraActive();
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -694,7 +699,7 @@
public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse()
throws Exception {
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
+ mActivity = setUpActivityWithComponent();
assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -705,8 +710,11 @@
public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse()
throws Exception {
mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
- doReturn(true).when(mActivity).isCameraActive();
- mController = new LetterboxUiController(mWm, mActivity);
+
+ mActivity = setUpActivityWithComponent();
+
+ doReturn(true).when(mActivity.mAppCompatController
+ .getAppCompatCameraOverrides()).isCameraActive();
assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides()
.shouldOverrideMinAspectRatioForCamera());
@@ -848,8 +856,8 @@
assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
mActivity.getParent().getConfiguration()), /* delta */ 0.01);
- spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
- doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ spyOn(mDisplayContent.mAppCompatCameraPolicy);
+ doReturn(true).when(mDisplayContent.mAppCompatCameraPolicy)
.isTreatmentEnabledForActivity(eq(mActivity));
assertEquals(mController.getSplitScreenAspectRatio(),
@@ -980,6 +988,7 @@
.setComponent(ComponentName.createRelative(mContext,
com.android.server.wm.LetterboxUiControllerTest.class.getName()))
.build();
+ spyOn(activity.mAppCompatController.getAppCompatCameraOverrides());
return activity;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 35cff7a..7efbc88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -25,11 +25,11 @@
import androidx.test.filters.SmallTest;
-import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.LogLevel;
+import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Ignore;
@@ -53,6 +53,9 @@
runWith(mockedProtoLog, this::testProtoLog);
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
anyInt(), eq(0b0010010111),
+ eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+ ? "Test completed successfully: %b %d %x %f %% %s"
+ : null),
eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index c962a3f..4220f31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -442,15 +442,7 @@
dc.getDisplayPolicy().release();
// Unregister SensorEventListener (foldable device may register for hinge angle).
dc.getDisplayRotation().onDisplayRemoved();
- if (dc.mDisplayRotationCompatPolicy != null) {
- dc.mDisplayRotationCompatPolicy.dispose();
- }
- if (dc.mCameraCompatFreeformPolicy != null) {
- dc.mCameraCompatFreeformPolicy.dispose();
- }
- if (dc.mCameraStateMonitor != null) {
- dc.mCameraStateMonitor.dispose();
- }
+ dc.mAppCompatCameraPolicy.dispose();
}
}
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index d4b6c91..feea55b 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -32,6 +32,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
/**
@@ -310,10 +312,18 @@
// This avoid breaking legacy code that rely on public-facing APIs to access cell location,
// and it doesn't create an info leak risk because the cell location is stored in the phone
// process anyway, and the system server already has location access.
- if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
- || query.callingUid == Process.NETWORK_STACK_UID
- || query.callingUid == Process.ROOT_UID) {
- return LocationPermissionResult.ALLOWED;
+ if (Flags.supportPhoneUidCheckForMultiuser()) {
+ if (TelephonyPermissions.isSystemOrPhone(query.callingUid)
+ || UserHandle.isSameApp(query.callingUid, Process.NETWORK_STACK_UID)
+ || UserHandle.isSameApp(query.callingUid, Process.ROOT_UID)) {
+ return LocationPermissionResult.ALLOWED;
+ }
+ } else {
+ if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
+ || query.callingUid == Process.NETWORK_STACK_UID
+ || query.callingUid == Process.ROOT_UID) {
+ return LocationPermissionResult.ALLOWED;
+ }
}
// Check the system-wide requirements. If the location main switch is off and the caller is
diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
index 796c82d..3e6f743 100644
--- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
+++ b/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java
@@ -43,12 +43,17 @@
/** carrier id */
private int mCarrierId;
+ /** apn */
+ private String mNiddApn;
+
/**
* @hide
*/
- public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId) {
+ public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId,
+ @NonNull String niddApn) {
this.mCarrierId = carrierId;
this.mSubscriberId = subscriberId;
+ this.mNiddApn = niddApn;
}
private ProvisionSubscriberId(Parcel in) {
@@ -63,6 +68,7 @@
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(mSubscriberId);
out.writeInt(mCarrierId);
+ out.writeString(mNiddApn);
}
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@@ -89,7 +95,7 @@
}
/**
- * @return token.
+ * @return provision subscriberId.
* @hide
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@@ -106,6 +112,15 @@
return mCarrierId;
}
+ /**
+ * @return niddApn.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public String getNiddApn() {
+ return mNiddApn;
+ }
+
@NonNull
@Override
public String toString() {
@@ -117,12 +132,16 @@
sb.append("CarrierId:");
sb.append(mCarrierId);
+ sb.append(",");
+
+ sb.append("NiddApn:");
+ sb.append(mNiddApn);
return sb.toString();
}
@Override
public int hashCode() {
- return Objects.hash(mSubscriberId, mCarrierId);
+ return Objects.hash(mSubscriberId, mCarrierId, mNiddApn);
}
@Override
@@ -131,11 +150,12 @@
if (o == null || getClass() != o.getClass()) return false;
ProvisionSubscriberId that = (ProvisionSubscriberId) o;
return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
- == that.mCarrierId;
+ == that.mCarrierId && mNiddApn.equals(that.mNiddApn);
}
private void readFromParcel(Parcel in) {
mSubscriberId = in.readString();
mCarrierId = in.readInt();
+ mNiddApn = in.readString();
}
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5a48327..5a27593 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
+import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, () -> {});
+ 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
}
@After
@@ -141,7 +142,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -158,7 +159,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -175,7 +176,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -191,7 +192,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -207,7 +208,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -276,7 +277,7 @@
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b1110101001010100,
+ 0b1110101001010100, null,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -301,7 +302,7 @@
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b01100100,
+ 0b01100100, null,
new Object[]{"test", 1, 0.1, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -325,7 +326,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
mProtoLog.startProtoLog(mock(PrintWriter.class));
mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b11, new Object[]{true});
+ 0b11, null, new Object[]{true});
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
try (InputStream is = new FileInputStream(mFile)) {
ProtoInputStream ip = new ProtoInputStream(is);
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index b6672a0..1d7b6b3 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -60,9 +60,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import perfetto.protos.Protolog;
-import perfetto.protos.ProtologCommon;
-
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -70,6 +67,9 @@
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
+
/**
* Test class for {@link ProtoLogImpl}.
*/
@@ -111,9 +111,6 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
- TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
- TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
-
mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
.addGroups(
Protolog.ProtoLogViewerConfig.Group.newBuilder()
@@ -160,9 +157,8 @@
mCacheUpdater = () -> {};
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
mProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, mReader,
+ viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
() -> mCacheUpdater.run());
- mProtoLog.registerGroups(TestProtoLogGroup.values());
}
@After
@@ -214,15 +210,15 @@
// Shouldn't be logging anything except WTF unless explicitly requested in the group
// override.
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -244,15 +240,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -278,15 +274,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -308,15 +304,15 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -333,14 +329,14 @@
}
@Test
- public void log_logcatEnabled() {
+ public void log_logcatEnabledExternalMessage() {
when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -357,17 +353,32 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO),
- eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
- + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
+ eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
verify(mReader).getViewerString(eq(1234L));
}
@Test
+ public void log_logcatEnabledInlineMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %d");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO), eq("test 5"));
+ verify(mReader, never()).getViewerString(anyLong());
+ }
+
+ @Test
public void log_logcatEnabledNoMessage() {
when(mReader.getViewerString(anyLong())).thenReturn(null);
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
@@ -375,11 +386,11 @@
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)"));
+ LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
verify(mReader).getViewerString(eq(1234L));
}
@@ -390,7 +401,7 @@
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -414,7 +425,7 @@
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b1110101001010100,
+ 0b1110101001010100, null,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -433,38 +444,6 @@
.isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true");
}
- @Test
- public void log_noProcessing() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
- long before;
- long after;
- try {
- traceMonitor.start();
- assertTrue(mProtoLog.isProtoEnabled());
-
- before = SystemClock.elapsedRealtimeNanos();
- mProtoLog.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
- "My test message :: %s, %d, %o, %x, %f, %b",
- "test", 1, 2, 3, 0.4, true);
- after = SystemClock.elapsedRealtimeNanos();
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
- .isAtLeast(before);
- Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
- .isAtMost(after);
- Truth.assertThat(protolog.messages.getFirst().getMessage())
- .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true");
- }
-
private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
final long messageId = new Random().nextLong();
mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
@@ -491,7 +470,7 @@
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b01100100,
+ 0b01100100, null,
new Object[]{"test", 1, 0.1, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -509,7 +488,7 @@
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, new Object[]{true});
+ 0b11, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -533,7 +512,7 @@
ProtoLogImpl.setSingleInstance(mProtoLog);
ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, true);
+ 0b11, null, true);
} finally {
traceMonitor.stop(mWriter);
}
@@ -607,7 +586,7 @@
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
PerfettoTraceMonitor traceMonitor1 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
@@ -685,53 +664,7 @@
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
- .isFalse();
- }
-
- @Test
- public void supportsNullString() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
- .build();
-
- try {
- traceMonitor.start();
-
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
- "My test null string: %s", null);
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.get(0).getMessage())
- .isEqualTo("My test null string: null");
- }
-
- @Test
- public void supportNullParams() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
- .build();
-
- try {
- traceMonitor.start();
-
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
- "My null args: %d, %f, %b", null, null, null);
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.get(0).getMessage())
- .isEqualTo("My null args: 0, 0, false");
+ .isTrue();
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 0496240..60456f9 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -58,50 +58,51 @@
public void d_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.DEBUG), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void v_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.VERBOSE), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void i_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.INFO), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void w_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
+ 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.WARN), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void e_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
@@ -109,10 +110,10 @@
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
- 1234, 4321);
+ 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.WTF), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 3c99e68..1087ae6 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -120,8 +120,6 @@
logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
call.name.toString(), call, context), groupMap.getValue(groupName))
- } else if (call.name.id == "initialize") {
- // No processing
} else {
// Process non-log message calls
otherCallVisitor?.processCall(call)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index c478f58..6a8a071 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -130,27 +130,28 @@
val hash = CodeUtils.hash(packagePath, messageString, level, group)
val newCall = call.clone()
- // Remove message string.
- // Out: ProtoLog.e(GROUP, args)
- newCall.arguments.removeAt(1)
+ if (!group.textEnabled) {
+ // Remove message string if text logging is not enabled by default.
+ // Out: ProtoLog.e(GROUP, null, arg)
+ newCall.arguments[1].replace(NameExpr("null"))
+ }
// Insert message string hash as a second argument.
- // Out: ProtoLog.e(GROUP, 1234, args)
+ // Out: ProtoLog.e(GROUP, 1234, null, arg)
newCall.arguments.add(1, LongLiteralExpr("" + hash + "L"))
val argTypes = LogDataType.parseFormatString(messageString)
val typeMask = LogDataType.logDataTypesToBitMask(argTypes)
// Insert bitmap representing which Number parameters are to be considered as
// floating point numbers.
- // Out: ProtoLog.e(GROUP, 1234, 0, args)
+ // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
// Replace call to a stub method with an actual implementation.
- // Out: ProtoLogImpl.e(GROUP, 1234, 0, args)
+ // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
newCall.setScope(protoLogImplClassNode)
if (argTypes.size != call.arguments.size - 2) {
throw InvalidProtoLogCallException(
"Number of arguments (${argTypes.size} does not match format" +
" string in: $call", ParsingContext(path, call))
}
- val argsOffset = 3
val blockStmt = BlockStmt()
if (argTypes.isNotEmpty()) {
// Assign every argument to a variable to check its type in compile time
@@ -159,9 +160,9 @@
argTypes.forEachIndexed { idx, type ->
val varName = "protoLogParam$idx"
val declaration = VariableDeclarator(getASTTypeForDataType(type), varName,
- getConversionForType(type)(newCall.arguments[idx + argsOffset].clone()))
+ getConversionForType(type)(newCall.arguments[idx + 4].clone()))
blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
- newCall.setArgument(idx + argsOffset, NameExpr(SimpleName(varName)))
+ newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
}
} else {
// Assign (Object[])null as the vararg parameter to prevent allocating an empty
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index 0cbbd48..2a83677 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -60,7 +60,7 @@
.containsMatch(Pattern.compile("\\{ String protoLogParam0 = " +
"String\\.valueOf\\(argString\\); long protoLogParam1 = argInt; " +
"com\\.android\\.internal\\.protolog.ProtoLogImpl_.*\\.d\\(" +
- "GROUP, -6872339441335321086L, 4, protoLogParam0, protoLogParam1" +
+ "GROUP, -6872339441335321086L, 4, null, protoLogParam0, protoLogParam1" +
"\\); \\}"))
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 6cde7a7..82aa93d 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -76,7 +76,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -86,7 +86,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -98,8 +98,8 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -109,7 +109,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, (Object[]) null); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -119,7 +119,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -129,7 +129,7 @@
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -172,12 +172,13 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
}
@@ -213,12 +214,13 @@
Truth.assertThat(protoLogCalls).hasSize(3)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT, out)
}
@@ -250,13 +252,13 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
- assertEquals("protoLogParam2", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
}
@@ -287,7 +289,7 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(4, methodCall.arguments.size)
+ assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("3218600869538902408L", methodCall.arguments[1].toString())
assertEquals(0.toString(), methodCall.arguments[2].toString())
@@ -321,12 +323,13 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
}
@@ -358,13 +361,14 @@
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
- assertEquals("protoLogParam2", methodCall.arguments[5].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
}
}