Merge "[Central Surfaces] Remove unused methods & variables from interface." into udc-qpr-dev
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 99d4064..b29e73a 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -292,12 +292,12 @@
packageName = mService.serviceInfo.packageName;
applicationInfo = mService.serviceInfo.applicationInfo;
}
- String contextUriString = pm.getText(
- packageName, mContextUriResource, applicationInfo).toString();
- if (contextUriString == null) {
+ CharSequence contextUriCharSequence = pm.getText(
+ packageName, mContextUriResource, applicationInfo);
+ if (contextUriCharSequence == null) {
return null;
}
- return Uri.parse(contextUriString);
+ return Uri.parse(contextUriCharSequence.toString());
}
/**
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 856bde8..36e0529 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -76,6 +76,20 @@
public final boolean disableFlushForViewTreeAppearing;
/**
+ * Is the content capture receiver enabled.
+ *
+ * @hide
+ */
+ public final boolean enableReceiver;
+
+ /**
+ * Options for the content protection flow.
+ *
+ * @hide
+ */
+ @NonNull public final ContentProtectionOptions contentProtectionOptions;
+
+ /**
* List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
* for all acitivites in the package).
*/
@@ -94,52 +108,99 @@
* for contexts belonging to the content capture service app.
*/
public ContentCaptureOptions(int loggingLevel) {
- this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
- /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
- /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+ this(
+ /* lite= */ true,
+ loggingLevel,
+ /* maxBufferSize= */ 0,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ /* enableReceiver= */ false,
+ new ContentProtectionOptions(
+ /* enableReceiver= */ false,
+ /* bufferSize= */ 0),
/* whitelistedComponents= */ null);
}
- /**
- * Default constructor.
- */
- public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
- int textChangeFlushingFrequencyMs, int logHistorySize,
- @SuppressLint({"ConcreteCollection", "NullableCollection"})
- @Nullable ArraySet<ComponentName> whitelistedComponents) {
- this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
- textChangeFlushingFrequencyMs, logHistorySize,
+ /** Default constructor. */
+ public ContentCaptureOptions(
+ int loggingLevel,
+ int maxBufferSize,
+ int idleFlushingFrequencyMs,
+ int textChangeFlushingFrequencyMs,
+ int logHistorySize,
+ @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+ ArraySet<ComponentName> whitelistedComponents) {
+ this(
+ /* lite= */ false,
+ loggingLevel,
+ maxBufferSize,
+ idleFlushingFrequencyMs,
+ textChangeFlushingFrequencyMs,
+ logHistorySize,
ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+ new ContentProtectionOptions(
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
whitelistedComponents);
}
/** @hide */
- public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
- int textChangeFlushingFrequencyMs, int logHistorySize,
+ public ContentCaptureOptions(
+ int loggingLevel,
+ int maxBufferSize,
+ int idleFlushingFrequencyMs,
+ int textChangeFlushingFrequencyMs,
+ int logHistorySize,
boolean disableFlushForViewTreeAppearing,
- @SuppressLint({"ConcreteCollection", "NullableCollection"})
- @Nullable ArraySet<ComponentName> whitelistedComponents) {
- this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
- textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+ boolean enableReceiver,
+ @NonNull ContentProtectionOptions contentProtectionOptions,
+ @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+ ArraySet<ComponentName> whitelistedComponents) {
+ this(
+ /* lite= */ false,
+ loggingLevel,
+ maxBufferSize,
+ idleFlushingFrequencyMs,
+ textChangeFlushingFrequencyMs,
+ logHistorySize,
+ disableFlushForViewTreeAppearing,
+ enableReceiver,
+ contentProtectionOptions,
whitelistedComponents);
}
/** @hide */
@VisibleForTesting
public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
- this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+ this(
+ ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+ new ContentProtectionOptions(
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
whitelistedComponents);
}
- private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
- int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+ private ContentCaptureOptions(
+ boolean lite,
+ int loggingLevel,
+ int maxBufferSize,
+ int idleFlushingFrequencyMs,
+ int textChangeFlushingFrequencyMs,
+ int logHistorySize,
boolean disableFlushForViewTreeAppearing,
- @Nullable ArraySet<ComponentName> whitelistedComponents) {
+ boolean enableReceiver,
+ @NonNull ContentProtectionOptions contentProtectionOptions,
+ @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+ ArraySet<ComponentName> whitelistedComponents) {
this.lite = lite;
this.loggingLevel = loggingLevel;
this.maxBufferSize = maxBufferSize;
@@ -147,6 +208,8 @@
this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
this.logHistorySize = logHistorySize;
this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
+ this.enableReceiver = enableReceiver;
+ this.contentProtectionOptions = contentProtectionOptions;
this.whitelistedComponents = whitelistedComponents;
}
@@ -191,12 +254,22 @@
return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
}
final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
- string.append("loggingLevel=").append(loggingLevel)
- .append(", maxBufferSize=").append(maxBufferSize)
- .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
- .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
- .append(", logHistorySize=").append(logHistorySize)
- .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
+ string.append("loggingLevel=")
+ .append(loggingLevel)
+ .append(", maxBufferSize=")
+ .append(maxBufferSize)
+ .append(", idleFlushingFrequencyMs=")
+ .append(idleFlushingFrequencyMs)
+ .append(", textChangeFlushingFrequencyMs=")
+ .append(textChangeFlushingFrequencyMs)
+ .append(", logHistorySize=")
+ .append(logHistorySize)
+ .append(", disableFlushForViewTreeAppearing=")
+ .append(disableFlushForViewTreeAppearing)
+ .append(", enableReceiver=")
+ .append(enableReceiver)
+ .append(", contentProtectionOptions=")
+ .append(contentProtectionOptions);
if (whitelistedComponents != null) {
string.append(", whitelisted=").append(whitelistedComponents);
}
@@ -210,11 +283,21 @@
pw.print(", lite");
return;
}
- pw.print(", bufferSize="); pw.print(maxBufferSize);
- pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
- pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
- pw.print(", logSize="); pw.print(logHistorySize);
- pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
+ pw.print(", bufferSize=");
+ pw.print(maxBufferSize);
+ pw.print(", idle=");
+ pw.print(idleFlushingFrequencyMs);
+ pw.print(", textIdle=");
+ pw.print(textChangeFlushingFrequencyMs);
+ pw.print(", logSize=");
+ pw.print(logHistorySize);
+ pw.print(", disableFlushForViewTreeAppearing=");
+ pw.print(disableFlushForViewTreeAppearing);
+ pw.print(", enableReceiver=");
+ pw.print(enableReceiver);
+ pw.print(", contentProtectionOptions=[");
+ contentProtectionOptions.dumpShort(pw);
+ pw.print("]");
if (whitelistedComponents != null) {
pw.print(", whitelisted="); pw.print(whitelistedComponents);
}
@@ -236,6 +319,8 @@
parcel.writeInt(textChangeFlushingFrequencyMs);
parcel.writeInt(logHistorySize);
parcel.writeBoolean(disableFlushForViewTreeAppearing);
+ parcel.writeBoolean(enableReceiver);
+ contentProtectionOptions.writeToParcel(parcel);
parcel.writeArraySet(whitelistedComponents);
}
@@ -254,12 +339,22 @@
final int textChangeFlushingFrequencyMs = parcel.readInt();
final int logHistorySize = parcel.readInt();
final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
+ final boolean enableReceiver = parcel.readBoolean();
+ final ContentProtectionOptions contentProtectionOptions =
+ ContentProtectionOptions.createFromParcel(parcel);
@SuppressWarnings("unchecked")
final ArraySet<ComponentName> whitelistedComponents =
(ArraySet<ComponentName>) parcel.readArraySet(null);
- return new ContentCaptureOptions(loggingLevel, maxBufferSize,
- idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
- disableFlushForViewTreeAppearing, whitelistedComponents);
+ return new ContentCaptureOptions(
+ loggingLevel,
+ maxBufferSize,
+ idleFlushingFrequencyMs,
+ textChangeFlushingFrequencyMs,
+ logHistorySize,
+ disableFlushForViewTreeAppearing,
+ enableReceiver,
+ contentProtectionOptions,
+ whitelistedComponents);
}
@Override
@@ -267,4 +362,62 @@
return new ContentCaptureOptions[size];
}
};
+
+ /**
+ * Content protection options for a given package.
+ *
+ * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
+ *
+ * @hide
+ */
+ public static class ContentProtectionOptions {
+
+ /**
+ * Is the content protection receiver enabled.
+ *
+ * @hide
+ */
+ public final boolean enableReceiver;
+
+ /**
+ * Size of the in-memory ring buffer for the content protection flow.
+ *
+ * @hide
+ */
+ public final int bufferSize;
+
+ public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+ this.enableReceiver = enableReceiver;
+ this.bufferSize = bufferSize;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
+ stringBuilder
+ .append("enableReceiver=")
+ .append(enableReceiver)
+ .append(", bufferSize=")
+ .append(bufferSize);
+ return stringBuilder.append(']').toString();
+ }
+
+ private void dumpShort(@NonNull PrintWriter pw) {
+ pw.print("enableReceiver=");
+ pw.print(enableReceiver);
+ pw.print(", bufferSize=");
+ pw.print(bufferSize);
+ }
+
+ private void writeToParcel(Parcel parcel) {
+ parcel.writeBoolean(enableReceiver);
+ parcel.writeInt(bufferSize);
+ }
+
+ private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+ boolean enableReceiver = parcel.readBoolean();
+ int bufferSize = parcel.readInt();
+ return new ContentProtectionOptions(enableReceiver, bufferSize);
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 96118f6..105b38a 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -444,6 +444,10 @@
* exist, it may be missing native code for the ABIs supported by the
* device, or it requires a newer SDK version, etc.
*
+ * Starting in {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, an app with only 32-bit native
+ * code can still be installed on a device that supports both 64-bit and 32-bit ABIs.
+ * However, a warning dialog will be displayed when the app is launched.
+ *
* @see #EXTRA_STATUS_MESSAGE
*/
public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 17bbe14..33960c0 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -266,7 +266,7 @@
*/
public static boolean useTouchpadTapToClick(@NonNull Context context) {
return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
+ Settings.System.TOUCHPAD_TAP_TO_CLICK, 1, UserHandle.USER_CURRENT) == 1;
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 7f0a666..4e086c2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1391,7 +1391,7 @@
Trace.endSection();
}
- if (redrawNeeded) {
+ if (redrawNeeded || sizeChanged) {
Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded");
onSurfaceRedrawNeeded(mSurfaceHolder);
Trace.endSection();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1af8ca2..c703af5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1285,6 +1285,8 @@
* </ul>
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ * <p>
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_X_OFFSET = 48;
@@ -1304,6 +1306,8 @@
* </ul>
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ * <p>
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
@@ -1324,14 +1328,29 @@
* </ul>
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ * <p>
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
+ /**
+ * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+ * <p>
+ * <ul>
+ * <li>For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+ * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+ * </ul>
+ * <p>
+ * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+ * the first pointer in a motion event.
+ * @hide
+ */
+ public static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
// NOTE: If you add a new axis here you must also add it to:
// frameworks/native/include/android/input.h
// frameworks/native/libs/input/InputEventLabels.cpp
- // platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
- // (testAxisFromToString)
+ // cts/tests/tests/view/src/android/view/cts/MotionEventTest.java (testAxisFromToString)
// Symbolic names of all axes.
private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
@@ -1387,6 +1406,7 @@
names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
+ names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT");
}
/**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ddaa71c..c11f497 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -786,7 +786,11 @@
mReleaseStack = null;
}
setUnreleasedWarningCallSite(callsite);
- addToRegistry();
+ if (nativeObject != 0) {
+ // Only add valid surface controls to the registry. This is called at the end of this
+ // method since its information is dumped if the process threshold is reached.
+ addToRegistry();
+ }
}
/**
@@ -893,6 +897,10 @@
"Only buffer layers can set a valid buffer size.");
}
+ if (mName == null) {
+ Log.w(TAG, "Missing name for SurfaceControl", new Throwable());
+ }
+
if ((mFlags & FX_SURFACE_MASK) == FX_SURFACE_NORMAL) {
setBLASTLayer();
}
@@ -1254,6 +1262,9 @@
}
/**
+ * Note: Most callers should use {@link SurfaceControl.Builder} or one of the other constructors
+ * to build an instance of a SurfaceControl. This constructor is mainly used for
+ * unparceling and passing into an AIDL call as an out parameter.
* @hide
*/
public SurfaceControl() {
@@ -2495,6 +2506,7 @@
public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
SurfaceControl sc = new SurfaceControl();
+ sc.mName = mirrorOf.mName + " (mirror)";
sc.assignNativeObject(nativeObj, "mirrorSurface");
return sc;
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index d987217..c8cf7d9 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -171,8 +171,7 @@
public SurfacePackage(@NonNull SurfacePackage other) {
SurfaceControl otherSurfaceControl = other.mSurfaceControl;
if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
- mSurfaceControl = new SurfaceControl();
- mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+ mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage");
}
mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
mInputToken = other.mInputToken;
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 668351b..c37e311 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -352,6 +352,30 @@
public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
"disable_flush_for_view_tree_appearing";
+ /**
+ * Enables the content protection receiver.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
+ "enable_content_protection_receiver";
+
+ /**
+ * Sets the size of the app blocklist for the content protection flow.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
+ "content_protection_apps_blocklist_size";
+
+ /**
+ * Sets the size of the in-memory ring buffer for the content protection flow.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
+ "content_protection_buffer_size";
+
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_OFF = 0;
@@ -384,6 +408,14 @@
public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
/** @hide */
public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
+ /** @hide */
+ public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
+ /** @hide */
+ public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
+ /** @hide */
+ public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 1000;
+ /** @hide */
+ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
private final Object mLock = new Object();
@@ -425,11 +457,11 @@
/** @hide */
static class StrippedContext {
- final String mPackageName;
- final String mContext;
+ @NonNull final String mPackageName;
+ @NonNull final String mContext;
final @UserIdInt int mUserId;
- private StrippedContext(Context context) {
+ private StrippedContext(@NonNull Context context) {
mPackageName = context.getPackageName();
mContext = context.toString();
mUserId = context.getUserId();
@@ -440,6 +472,7 @@
return mContext;
}
+ @NonNull
public String getPackageName() {
return mPackageName;
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
new file mode 100644
index 0000000..9abf6f1
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+/**
+ * Utilities for reading data from {@link ContentCaptureEvent} and {@link ViewNode}.
+ *
+ * @hide
+ */
+public final class ContentProtectionUtils {
+
+ /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+ @Nullable
+ public static String getEventText(@NonNull ContentCaptureEvent event) {
+ CharSequence text = event.getText();
+ if (text == null) {
+ return null;
+ }
+ return text.toString();
+ }
+
+ /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+ @Nullable
+ public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
+ ViewNode viewNode = event.getViewNode();
+ if (viewNode == null) {
+ return null;
+ }
+ return getViewNodeText(viewNode);
+ }
+
+ /** Returns the text extracted directly from the {@link ViewNode}, if set. */
+ @Nullable
+ public static String getViewNodeText(@NonNull ViewNode viewNode) {
+ CharSequence text = viewNode.getText();
+ if (text == null) {
+ return null;
+ }
+ return text.toString();
+ }
+}
diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/core/java/android/view/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 41ef44e..40b060a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4361,15 +4361,14 @@
* @param icProto {@link InputConnection} call data in proto format.
* @hide
*/
- @GuardedBy("mH")
public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
- if (!isImeSessionAvailableLocked()) {
- return;
- }
-
- proto.write(DISPLAY_ID, mDisplayId);
- final long token = proto.start(INPUT_METHOD_MANAGER);
synchronized (mH) {
+ if (!isImeSessionAvailableLocked()) {
+ return;
+ }
+
+ proto.write(DISPLAY_ID, mDisplayId);
+ final long token = proto.start(INPUT_METHOD_MANAGER);
proto.write(CUR_ID, mCurBindState.mImeId);
proto.write(FULLSCREEN_MODE, mFullscreenMode);
proto.write(ACTIVE, mActive);
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dedbf50..7f96266 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1850,7 +1850,7 @@
}
@Override
- public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
switch (this.type) {
case URI:
final Uri uri = (Uri) getParameterValue(null);
@@ -2313,6 +2313,14 @@
public int getActionTag() {
return NIGHT_MODE_REFLECTION_ACTION_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ if (this.type == ICON) {
+ visitIconUri((Icon) mDarkValue, visitor);
+ visitIconUri((Icon) mLightValue, visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 52e17ca..f40874b 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -43,6 +43,7 @@
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.getNavigationBarRect;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -183,7 +184,7 @@
// We consider nearly matched dimensions as there can be rounding errors and the user
// won't notice very minute differences from scaling one dimension more than the other
- final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
+ boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
// Keep a reference to it such that it doesn't get destroyed when finalized.
SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
@@ -199,8 +200,20 @@
// still hidden.
mTransaction.show(childSurfaceControl);
if (aspectRatioMismatch) {
- // Clip off ugly navigation bar.
- final Rect crop = calculateSnapshotCrop();
+ Rect crop = null;
+ final Rect letterboxInsets = mSnapshot.getLetterboxInsets();
+ if (letterboxInsets.left != 0 || letterboxInsets.top != 0
+ || letterboxInsets.right != 0 || letterboxInsets.bottom != 0) {
+ // Clip off letterbox.
+ crop = calculateSnapshotCrop(letterboxInsets);
+ // If the snapshot can cover the frame, then no need to draw background.
+ aspectRatioMismatch = !isAspectRatioMatch(mFrame, crop);
+ }
+ // if letterbox doesn't match window frame, try crop by content insets
+ if (aspectRatioMismatch) {
+ // Clip off ugly navigation bar.
+ crop = calculateSnapshotCrop(mSnapshot.getContentInsets());
+ }
frame = calculateSnapshotFrame(crop);
mTransaction.setWindowCrop(childSurfaceControl, crop);
mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
@@ -242,14 +255,13 @@
/**
* Calculates the snapshot crop in snapshot coordinate space.
- *
+ * @param insets Content insets or Letterbox insets
* @return crop rect in snapshot coordinate space.
*/
- Rect calculateSnapshotCrop() {
+ Rect calculateSnapshotCrop(@NonNull Rect insets) {
final Rect rect = new Rect();
final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
- final Rect insets = mSnapshot.getContentInsets();
final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
@@ -334,6 +346,15 @@
- ((float) frame.width() / frame.height())) <= 0.01f;
}
+ private static boolean isAspectRatioMatch(Rect frame1, Rect frame2) {
+ if (frame1.isEmpty() || frame2.isEmpty()) {
+ return false;
+ }
+ return Math.abs(
+ ((float) frame2.width() / frame2.height())
+ - ((float) frame1.width() / frame1.height())) <= 0.01f;
+ }
+
/**
* Get or create a TaskDescription from a RunningTaskInfo.
*/
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index dfdff9e..5d14698 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.Trace;
import android.util.ArraySet;
@@ -800,22 +801,25 @@
}
private void addTimeout() {
+ Looper looper = null;
synchronized (sHandlerThreadLock) {
if (sHandlerThread == null) {
sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer");
sHandlerThread.start();
}
+
+ looper = sHandlerThread.getLooper();
}
synchronized (mLock) {
- if (mTimeoutAdded || mTimeoutDisabled) {
+ if (mTimeoutAdded || mTimeoutDisabled || looper == null) {
// We only need one timeout for the entire SurfaceSyncGroup since we just want to
// ensure it doesn't stay stuck forever.
return;
}
if (mHandler == null) {
- mHandler = new Handler(sHandlerThread.getLooper());
+ mHandler = new Handler(looper);
}
mTimeoutAdded = true;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 22b2ec0..421d998 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -31,6 +31,8 @@
import android.view.IWindow;
import android.view.IWindowSession;
+import androidx.annotation.VisibleForTesting;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -66,7 +68,9 @@
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
- private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
+
+ @VisibleForTesting
+ public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private Checker mChecker;
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 1f7640d..7c4252e 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -258,6 +258,10 @@
}
private static int convertToLoggingMagnificationScale(float scale) {
- return (int) (scale * 100);
+ // per b/269366674, we make every 10% a bucket for both privacy and readability concern.
+ // For example
+ // 1. both 2.30f(230%) and 2.36f(236%) would return 230 as bucket id.
+ // 2. bucket id 370 means scale range in [370%, 379%]
+ return ((int) (scale * 10)) * 10;
}
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 86c2893..3d95dd3 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -80,7 +80,7 @@
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
- devFlag("persist.sysui.notification.log_dnd_state_events");
+ releasedFlag("persist.sysui.notification.log_dnd_state_events");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml
index 58042f9..54ac677 100644
--- a/core/res/res/layout/language_picker_section_header.xml
+++ b/core/res/res/layout/language_picker_section_header.xml
@@ -25,4 +25,5 @@
android:textColor="?android:attr/colorAccent"
android:textStyle="bold"
android:id="@+id/language_picker_header"
- tools:text="@string/language_picker_section_all"/>
+ tools:text="@string/language_picker_section_all"
+ android:scrollbars="none"/>
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index c6f4fa2..f8348d2 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Parcel;
import android.util.ArraySet;
import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
@@ -40,16 +41,29 @@
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureOptionsTest {
- private final ComponentName mContextComponent = new ComponentName("marco", "polo");
- private final ComponentName mComp1 = new ComponentName("comp", "one");
- private final ComponentName mComp2 = new ComponentName("two", "comp");
+ private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
+ private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
+ private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+ private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
+ new ContentCaptureOptions(
+ /* loggingLevel= */ 1000,
+ /* maxBufferSize= */ 1001,
+ /* idleFlushingFrequencyMs= */ 1002,
+ /* textChangeFlushingFrequencyMs= */ 1003,
+ /* logHistorySize= */ 1004,
+ /* disableFlushForViewTreeAppearing= */ true,
+ /* enableReceiver= */ false,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ /* bufferSize= */ 2001),
+ /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
@Mock private Context mContext;
@Mock private ContentCaptureClient mClient;
@Before
public void setExpectation() {
- when(mClient.contentCaptureClientGetComponentName()).thenReturn(mContextComponent);
+ when(mClient.contentCaptureClientGetComponentName()).thenReturn(CONTEXT_COMPONENT);
when(mContext.getContentCaptureClient()).thenReturn(mClient);
}
@@ -67,26 +81,27 @@
@Test
public void testIsWhitelisted_notWhitelisted() {
- ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mComp2));
+ ContentCaptureOptions options = new ContentCaptureOptions(toSet(COMPONENT1, COMPONENT2));
assertThat(options.isWhitelisted(mContext)).isFalse();
}
@Test
public void testIsWhitelisted_whitelisted() {
- ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mContextComponent));
+ ContentCaptureOptions options =
+ new ContentCaptureOptions(toSet(COMPONENT1, CONTEXT_COMPONENT));
assertThat(options.isWhitelisted(mContext)).isTrue();
}
@Test
public void testIsWhitelisted_invalidContext() {
- ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+ ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
Context invalidContext = mock(Context.class); // has no client
assertThat(options.isWhitelisted(invalidContext)).isFalse();
}
@Test
public void testIsWhitelisted_clientWithNullComponentName() {
- ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+ ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
ContentCaptureClient client = mock(ContentCaptureClient.class);
Context context = mock(Context.class);
when(context.getContentCaptureClient()).thenReturn(client);
@@ -94,8 +109,69 @@
assertThat(options.isWhitelisted(context)).isFalse();
}
+ @Test
+ public void testToString() {
+ String actual = CONTENT_CAPTURE_OPTIONS.toString();
+
+ String expected =
+ new StringBuilder("ContentCaptureOptions [")
+ .append("loggingLevel=")
+ .append(CONTENT_CAPTURE_OPTIONS.loggingLevel)
+ .append(", maxBufferSize=")
+ .append(CONTENT_CAPTURE_OPTIONS.maxBufferSize)
+ .append(", idleFlushingFrequencyMs=")
+ .append(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs)
+ .append(", textChangeFlushingFrequencyMs=")
+ .append(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs)
+ .append(", logHistorySize=")
+ .append(CONTENT_CAPTURE_OPTIONS.logHistorySize)
+ .append(", disableFlushForViewTreeAppearing=")
+ .append(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing)
+ .append(", enableReceiver=")
+ .append(CONTENT_CAPTURE_OPTIONS.enableReceiver)
+ .append(", contentProtectionOptions=ContentProtectionOptions [")
+ .append("enableReceiver=")
+ .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
+ .append(", bufferSize=")
+ .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+ .append("], whitelisted=")
+ .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
+ .append(']')
+ .toString();
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testParcelSerializationDeserialization() {
+ Parcel parcel = Parcel.obtain();
+ CONTENT_CAPTURE_OPTIONS.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ ContentCaptureOptions actual = ContentCaptureOptions.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.loggingLevel).isEqualTo(CONTENT_CAPTURE_OPTIONS.loggingLevel);
+ assertThat(actual.maxBufferSize).isEqualTo(CONTENT_CAPTURE_OPTIONS.maxBufferSize);
+ assertThat(actual.idleFlushingFrequencyMs)
+ .isEqualTo(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs);
+ assertThat(actual.textChangeFlushingFrequencyMs)
+ .isEqualTo(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs);
+ assertThat(actual.logHistorySize).isEqualTo(CONTENT_CAPTURE_OPTIONS.logHistorySize);
+ assertThat(actual.disableFlushForViewTreeAppearing)
+ .isEqualTo(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing);
+ assertThat(actual.enableReceiver).isEqualTo(CONTENT_CAPTURE_OPTIONS.enableReceiver);
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver)
+ .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
+ assertThat(actual.contentProtectionOptions.bufferSize)
+ .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+ assertThat(actual.whitelistedComponents)
+ .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
+ }
+
@NonNull
- private ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
+ private static ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
ArraySet<ComponentName> set = new ArraySet<>();
if (comps != null) {
for (int i = 0; i < comps.length; i++) {
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index a69c6ff..6f38b84 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -5,3 +5,5 @@
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS
per-file *ComponentCallbacks* = charlesccchen@google.com
+per-file ContentCaptureOptions* = file:/core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index d10ba7c..e117051 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -104,6 +104,18 @@
}
@Test
+ public void testInvalidSurfaceControlNotAddedToRegistry() {
+ int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode();
+ // Verify no changes to the registry when dealing with invalid surface controls
+ SurfaceControl sc0 = new SurfaceControl();
+ SurfaceControl sc1 = new SurfaceControl(sc0, "test");
+ assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
+ sc0.release();
+ sc1.release();
+ assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
+ }
+
+ @Test
public void testThresholds() {
SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
TestReporter reporter = new TestReporter();
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
new file mode 100644
index 0000000..1459799
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link ContentProtectionUtils}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionUtilsTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionUtilsTest {
+
+ private static final String TEXT = "TEST_TEXT";
+
+ private static final ContentCaptureEvent EVENT = createEvent();
+
+ private static final ViewNode VIEW_NODE = new ViewNode();
+
+ private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+
+ @Test
+ public void event_getEventText_null() {
+ String actual = ContentProtectionUtils.getEventText(EVENT);
+
+ assertThat(actual).isNull();
+ }
+
+ @Test
+ public void event_getEventText_notNull() {
+ ContentCaptureEvent event = createEvent();
+ event.setText(TEXT);
+
+ String actual = ContentProtectionUtils.getEventText(event);
+
+ assertThat(actual).isEqualTo(TEXT);
+ }
+
+ @Test
+ public void event_getViewNodeText_null() {
+ String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+
+ assertThat(actual).isNull();
+ }
+
+ @Test
+ public void event_getViewNodeText_notNull() {
+ ContentCaptureEvent event = createEvent();
+ event.setViewNode(VIEW_NODE_WITH_TEXT);
+
+ String actual = ContentProtectionUtils.getViewNodeText(event);
+
+ assertThat(actual).isEqualTo(TEXT);
+ }
+
+ @Test
+ public void viewNode_getViewNodeText_null() {
+ String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+
+ assertThat(actual).isNull();
+ }
+
+ @Test
+ public void viewNode_getViewNodeText_notNull() {
+ String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+
+ assertThat(actual).isEqualTo(TEXT);
+ }
+
+ private static ContentCaptureEvent createEvent() {
+ return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
+ }
+
+ private static ViewNode createViewNodeWithText() {
+ View view = new View(ApplicationProvider.getApplicationContext());
+ ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+ viewStructure.setText(TEXT);
+ return viewStructure.getNode();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index b42a4bd..73aa936 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -717,6 +717,19 @@
}
@Test
+ public void visitUris_themedIcons() {
+ RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+ final Icon iconLight = Icon.createWithContentUri("content://light/icon");
+ final Icon iconDark = Icon.createWithContentUri("content://dark/icon");
+ views.setIcon(R.id.layout, "setLargeIcon", iconLight, iconDark);
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ views.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(iconLight.getUri()));
+ verify(visitor, times(1)).accept(eq(iconDark.getUri()));
+ }
+
+ @Test
public void visitUris_nestedViews() {
final RemoteViews outer = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 6764ac8..0361546 100644
--- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -158,32 +158,42 @@
@Test
public void testCalculateSnapshotCrop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+ final Rect contentInsets = new Rect(0, 10, 0, 10);
+ setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 100, 90),
+ mSnapshotSurface.calculateSnapshotCrop(contentInsets));
}
@Test
public void testCalculateSnapshotCrop_taskNotOnTop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150));
- assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
+ final Rect contentInsets = new Rect(0, 10, 0, 10);
+ setupSurface(100, 100, contentInsets, 0, new Rect(0, 50, 100, 150));
+ assertEquals(new Rect(0, 10, 100, 90),
+ mSnapshotSurface.calculateSnapshotCrop(contentInsets));
}
@Test
public void testCalculateSnapshotCrop_navBarLeft() {
- setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop());
+ final Rect contentInsets = new Rect(0, 10, 0, 0);
+ setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(10, 0, 100, 100),
+ mSnapshotSurface.calculateSnapshotCrop(contentInsets));
}
@Test
public void testCalculateSnapshotCrop_navBarRight() {
- setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop());
+ final Rect contentInsets = new Rect(0, 10, 10, 0);
+ setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 90, 100),
+ mSnapshotSurface.calculateSnapshotCrop(contentInsets));
}
@Test
public void testCalculateSnapshotCrop_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop());
+ final Rect contentInsets = new Rect(5, 10, 5, 10);
+ setupSurface(100, 100, contentInsets, 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(5, 0, 95, 90),
+ mSnapshotSurface.calculateSnapshotCrop(contentInsets));
}
@Test
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 8e772a2..2ef2d3a 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -16,12 +16,15 @@
package android.window;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
+import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -45,6 +48,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Tests for {@link WindowOnBackInvokedDispatcherTest}
*
@@ -69,6 +75,8 @@
@Mock
private ApplicationInfo mApplicationInfo;
+ private int mCallbackInfoCalls = 0;
+
private final BackMotionEvent mBackEvent = new BackMotionEvent(
/* touchX = */ 0,
/* touchY = */ 0,
@@ -93,105 +101,243 @@
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
+ private List<OnBackInvokedCallbackInfo> captureCallbackInfo() throws RemoteException {
+ ArgumentCaptor<OnBackInvokedCallbackInfo> captor = ArgumentCaptor
+ .forClass(OnBackInvokedCallbackInfo.class);
+ // atLeast(0) -> get all setOnBackInvokedCallbackInfo() invocations
+ verify(mWindowSession, atLeast(0))
+ .setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+ verifyNoMoreInteractions(mWindowSession);
+ return captor.getAllValues();
+ }
+
+ private OnBackInvokedCallbackInfo assertSetCallbackInfo() throws RemoteException {
+ List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo();
+ int actual = callbackInfos.size();
+ assertEquals("setOnBackInvokedCallbackInfo", ++mCallbackInfoCalls, actual);
+ return callbackInfos.get(mCallbackInfoCalls - 1);
+ }
+
+ private void assertNoSetCallbackInfo() throws RemoteException {
+ List<OnBackInvokedCallbackInfo> callbackInfos = captureCallbackInfo();
+ int actual = callbackInfos.size();
+ assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual);
+ }
+
+ private void assertCallbacksSize(int expectedDefault, int expectedOverlay) {
+ ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher
+ .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT);
+ int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0;
+ assertEquals("mOnBackInvokedCallbacks DEFAULT size", expectedDefault, actualSizeDefault);
+
+ ArrayList<OnBackInvokedCallback> callbacksOverlay = mDispatcher
+ .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY);
+ int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0;
+ assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay);
+ }
+
+ private void assertTopCallback(OnBackInvokedCallback expectedCallback) {
+ assertEquals("topCallback", expectedCallback, mDispatcher.getTopCallback());
+ }
+
+ @Test
+ public void registerCallback_samePriority_sameCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ // The callback is removed and added again
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
+ }
+
+ @Test
+ public void registerCallback_samePriority_differentCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ // The new callback becomes the TopCallback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback2);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
+ verifyNoMoreInteractions(mCallback2);
+ }
+
+ @Test
+ public void registerCallback_differentPriority_sameCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ // The callback is moved to the new priority list
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
+ }
+
+ @Test
+ public void registerCallback_differentPriority_differentCallback() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+ assertSetCallbackInfo();
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertTopCallback(mCallback1);
+
+ // The callback with higher priority is still the TopCallback
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertNoSetCallbackInfo();
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertTopCallback(mCallback1);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
+ verifyNoMoreInteractions(mCallback2);
+ }
+
+ @Test
+ public void registerCallback_sameInstanceAddedTwice() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+ assertCallbacksSize(/* default */ 0, /* overlay */ 1);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertNoSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertCallbacksSize(/* default */ 2, /* overlay */ 0);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback1);
+
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
+ assertCallbacksSize(/* default */ 1, /* overlay */ 1);
+ assertSetCallbackInfo();
+ assertTopCallback(mCallback2);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
+ verifyNoMoreInteractions(mCallback2);
+ }
+
@Test
public void propagatesTopCallback_samePriority() throws RemoteException {
- ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
- ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ OnBackInvokedCallbackInfo callbackInfo2 = assertSetCallbackInfo();
- verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
- Mockito.eq(mWindow),
- captor.capture());
- captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
+ callbackInfo1.getCallback().onBackStarted(mBackEvent);
+
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
- captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
+ callbackInfo2.getCallback().onBackStarted(mBackEvent);
+
waitForIdle();
verify(mCallback2).onBackStarted(any(BackEvent.class));
+
+ // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() ->
+ // Spring.animateToFinalPosition(0). This causes a progress event to be fired.
+ verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@Test
public void propagatesTopCallback_differentPriority() throws RemoteException {
- ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
- ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback1);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
- verify(mWindowSession).setOnBackInvokedCallbackInfo(
- Mockito.eq(mWindow), captor.capture());
verifyNoMoreInteractions(mWindowSession);
- assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
- captor.getValue().getCallback().onBackStarted(mBackEvent);
+ assertEquals(callbackInfo.getPriority(), PRIORITY_OVERLAY);
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
public void propagatesTopCallback_withRemoval() throws RemoteException {
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertSetCallbackInfo();
- reset(mWindowSession);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertSetCallbackInfo();
+
mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
- verifyZeroInteractions(mWindowSession);
+
+ waitForIdle();
+ verifyNoMoreInteractions(mWindowSession);
+ verifyNoMoreInteractions(mCallback1);
mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+
+ waitForIdle();
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
}
@Test
public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException {
- ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
- ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1);
+ assertSetCallbackInfo();
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2);
+ assertNoSetCallbackInfo();
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ assertSetCallbackInfo();
- mDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY,
- mCallback1
- );
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback2);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2);
- reset(mWindowSession);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
- verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
- captor.getValue().getCallback().onBackStarted(mBackEvent);
+ OnBackInvokedCallbackInfo lastCallbackInfo = assertSetCallbackInfo();
+
+ lastCallbackInfo.getCallback().onBackStarted(mBackEvent);
+
waitForIdle();
verify(mCallback2).onBackStarted(any(BackEvent.class));
}
@Test
public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException {
- ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
- ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
- verify(mWindowSession).setOnBackInvokedCallbackInfo(
- Mockito.eq(mWindow),
- captor.capture());
- IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback();
- iOnBackInvokedCallback.onBackStarted(mBackEvent);
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
+
+ waitForIdle();
verify(mCallback1).onBackCancelled();
- verifyNoMoreInteractions(mCallback1);
+ verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3b45d5d..4cedd41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -308,7 +308,8 @@
forAllTaskContainers(taskContainer -> {
synchronized (mLock) {
- final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+ final List<TaskFragmentContainer> containers =
+ taskContainer.getTaskFragmentContainers();
// Clean up the TaskFragmentContainers by the z-order from the lowest.
for (int i = 0; i < containers.size(); i++) {
final TaskFragmentContainer container = containers.get(i);
@@ -611,8 +612,7 @@
@NonNull TaskContainer taskContainer) {
// Update all TaskFragments in the Task. Make a copy of the list since some may be
// removed on updating.
- final List<TaskFragmentContainer> containers =
- new ArrayList<>(taskContainer.mContainers);
+ final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
for (int i = containers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
@@ -1331,7 +1331,8 @@
// Check pending appeared activity first because there can be a delay for the server
// update.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (container.hasPendingAppearedActivity(activityToken)) {
@@ -1342,7 +1343,8 @@
// Check appeared activity if there is no such pending appeared activity.
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (container.hasAppearedActivity(activityToken)) {
@@ -1472,7 +1474,7 @@
void removeContainers(@NonNull TaskContainer taskContainer,
@NonNull List<TaskFragmentContainer> containers) {
// Remove all split containers that included this one
- taskContainer.mContainers.removeAll(containers);
+ taskContainer.removeTaskFragmentContainers(containers);
// Marked as a pending removal which will be removed after it is actually removed on the
// server side (#onTaskFragmentVanished).
// In this way, we can keep track of the Task bounds until we no longer have any
@@ -1497,7 +1499,9 @@
taskContainer.removeSplitContainers(containersToRemove);
// Cleanup any dependent references.
- for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
+ final List<TaskFragmentContainer> taskFragmentContainers =
+ taskContainer.getTaskFragmentContainers();
+ for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
containerToUpdate.removeContainersToFinishOnExit(containers);
}
}
@@ -1536,8 +1540,9 @@
if (taskContainer == null) {
return null;
}
- for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+ for (int i = containers.size() - 1; i >= 0; i--) {
+ final TaskFragmentContainer container = containers.get(i);
if (!container.isFinished() && (container.getRunningActivityCount() > 0
// We may be waiting for the top TaskFragment to become non-empty after
// creation. In that case, we don't want to treat the TaskFragment below it as
@@ -1933,7 +1938,8 @@
@GuardedBy("mLock")
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .getTaskFragmentContainers();
for (TaskFragmentContainer container : containers) {
if (container.getTaskFragmentToken().equals(fragmentToken)) {
return container;
@@ -2094,7 +2100,7 @@
}
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
- .mContainers;
+ .getTaskFragmentContainers();
for (int j = containers.size() - 1; j >= 0; j--) {
final TaskFragmentContainer container = containers.get(j);
if (!container.hasActivity(activityToken)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 0626510..4580c98 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -51,7 +51,7 @@
/** Active TaskFragments in this Task. */
@NonNull
- final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+ private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
/** Active split pairs in this Task. */
@NonNull
@@ -207,9 +207,13 @@
return false;
}
+ /**
+ * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the
+ * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead.
+ */
@NonNull
List<SplitContainer> getSplitContainers() {
- return new ArrayList<>(mSplitContainers);
+ return mSplitContainers;
}
void addSplitContainer(@NonNull SplitContainer splitContainer) {
@@ -220,6 +224,36 @@
mSplitContainers.removeAll(containers);
}
+ void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.add(taskFragmentContainer);
+ }
+
+ void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.add(index, taskFragmentContainer);
+ }
+
+ void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+ mContainers.remove(taskFragmentContainer);
+ }
+
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
+ mContainers.removeAll(taskFragmentContainer);
+ }
+
+ void clearTaskFragmentContainer() {
+ mContainers.clear();
+ }
+
+ /**
+ * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on
+ * the returned list. Use {@link #addTaskFragmentContainer},
+ * {@link #removeTaskFragmentContainer} or other related methods instead.
+ */
+ @NonNull
+ List<TaskFragmentContainer> getTaskFragmentContainers() {
+ return mContainers;
+ }
+
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 60be9d1..61df335 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -180,23 +180,25 @@
throw new IllegalArgumentException(
"pairedPrimaryContainer must be in the same Task");
}
- final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
- taskContainer.mContainers.add(primaryIndex + 1, this);
+ final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
+ taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
} else if (pendingAppearedActivity != null) {
// The TaskFragment will be positioned right above the pending appeared Activity. If any
// existing TaskFragment is empty with pending Intent, it is likely that the Activity of
// the pending Intent hasn't been created yet, so the new Activity should be below the
// empty TaskFragment.
- int i = taskContainer.mContainers.size() - 1;
+ final List<TaskFragmentContainer> containers =
+ taskContainer.getTaskFragmentContainers();
+ int i = containers.size() - 1;
for (; i >= 0; i--) {
- final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+ final TaskFragmentContainer container = containers.get(i);
if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
break;
}
}
- taskContainer.mContainers.add(i + 1, this);
+ taskContainer.addTaskFragmentContainer(i + 1, this);
} else {
- taskContainer.mContainers.add(this);
+ taskContainer.addTaskFragmentContainer(this);
}
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 82692a5..9e26472 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -183,23 +183,23 @@
// tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.mContainers.add(tf2);
+ taskContainer.addTaskFragmentContainer(tf2);
// tf3 is finished so is not active.
final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
doReturn(true).when(tf3).isFinished();
doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.mContainers.add(tf3);
+ taskContainer.addTaskFragmentContainer(tf3);
mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
assertWithMessage("Must return tf2 because tf3 is not active.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
- taskContainer.mContainers.remove(tf3);
+ taskContainer.removeTaskFragmentContainer(tf3);
assertWithMessage("Must return tf2 because tf2 has running activity.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
- taskContainer.mContainers.remove(tf2);
+ taskContainer.removeTaskFragmentContainer(tf2);
assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
@@ -365,7 +365,8 @@
final Activity r1 = createMockActivity();
addSplitTaskFragments(r0, r1);
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
- final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ final TaskFragmentContainer taskFragmentContainer =
+ taskContainer.getTaskFragmentContainers().get(0);
spyOn(taskContainer);
// No update when the Task is invisible.
@@ -1092,7 +1093,7 @@
verify(mTransaction).finishActivity(mActivity.getActivityToken());
verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
- assertTrue(taskContainer.mContainers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
assertTrue(taskContainer.getSplitContainers().isEmpty());
}
@@ -1365,15 +1366,13 @@
TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
- List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
- .mContainers;
-
- assertEquals(containers.get(0), tf);
+ final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+ assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf);
mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
- assertTrue(containers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
}
@Test
@@ -1383,10 +1382,8 @@
bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
- List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
- .mContainers;
-
- assertEquals(containers.size(), 2);
+ final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+ assertEquals(taskContainer.getTaskFragmentContainers().size(), 2);
Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
@@ -1405,7 +1402,7 @@
+ "regardless of the order in ActivityStack set",
topTf.getTaskFragmentToken(), fragmentTokens.get(1));
- assertTrue(containers.isEmpty());
+ assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 13e7092..11af1d1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -127,7 +127,7 @@
assertFalse(taskContainer.isEmpty());
taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
- taskContainer.mContainers.clear();
+ taskContainer.clearTaskFragmentContainer();
assertFalse(taskContainer.isEmpty());
}
@@ -152,13 +152,13 @@
assertNull(taskContainer.getTopNonFinishingActivity());
final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
- taskContainer.mContainers.add(tf0);
+ taskContainer.addTaskFragmentContainer(tf0);
final Activity activity0 = mock(Activity.class);
doReturn(activity0).when(tf0).getTopNonFinishingActivity();
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
- taskContainer.mContainers.add(tf1);
+ taskContainer.addTaskFragmentContainer(tf1);
assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
final Activity activity1 = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
new file mode 100644
index 0000000..d99d64d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <ripple android:color="#99999999">
+ <item android:drawable="@drawable/bubble_manage_menu_bg" />
+ </ripple>
+ </item>
+ <item android:drawable="@drawable/bubble_manage_menu_bg" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..f450846
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
+ android:fillColor="#5F6368"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
new file mode 100644
index 0000000..ddcd5c6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
+ android:background="@drawable/bubble_manage_menu_row">
+
+ <ImageView
+ android:id="@+id/bubble_bar_menu_item_icon"
+ android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/bubble_bar_menu_item_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
new file mode 100644
index 0000000..82e5aee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
+ android:orientation="vertical"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
+ android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
+ android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/bubble_bar_manage_menu_bubble_section"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingStart="14dp"
+ android:paddingEnd="12dp"
+ android:background="@drawable/bubble_manage_menu_section"
+ android:elevation="@dimen/bubble_manage_menu_elevation">
+
+ <ImageView
+ android:id="@+id/bubble_bar_manage_menu_bubble_icon"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
+ android:contentDescription="@null" />
+
+ <TextView
+ android:id="@+id/bubble_bar_manage_menu_bubble_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_weight="1"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+ <ImageView
+ android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
+ android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+ android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+ android:layout_marginStart="8dp"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_expand_less"
+ app:tint="?android:attr/textColorPrimary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/bubble_bar_manage_menu_actions_section"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+ android:background="@drawable/bubble_manage_menu_bg"
+ android:elevation="@dimen/bubble_manage_menu_elevation" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6f8a766..f2a0785 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -32,6 +32,7 @@
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
<color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
<color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
+ <color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
<!-- PiP -->
<color name="pip_custom_close_bg">#D93025</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index aa05179..2141259 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -234,6 +234,20 @@
<dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
<!-- The height of the drag handle shown along with a bubble bar expanded view. -->
<dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
+ <!-- Minimum width of the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+ <!-- Size of the dismiss icon in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
+ <!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
+ <dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
+ <!-- Top padding of the bubble bar manage menu -->
+ <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
+ <!-- Spacing between sections of the bubble bar manage menu -->
+ <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
+ <!-- Height of an item in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
+ <!-- Size of the icons in the bubble bar manage menu. -->
+ <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 102f2cb..504839f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -855,7 +855,8 @@
return mIsAppBubble;
}
- Intent getSettingsIntent(final Context context) {
+ /** Creates open app settings intent */
+ public Intent getSettingsIntent(final Context context) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
final int uid = getUid(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 988ad8c..39338e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -720,6 +720,7 @@
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
if (mStackView == null) {
@@ -1221,6 +1222,13 @@
}
/**
+ * Dismiss bubble if it exists and remove it from the stack
+ */
+ public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
+ mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+ }
+
+ /**
* Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
* can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 8ab9841..3a46797 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -192,6 +192,11 @@
}
info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
+ float[] bubbleBitmapScale = new float[1];
+ info.bubbleBitmap = iconFactory.getBubbleBitmap(
+ iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+ b.getIcon()), bubbleBitmapScale);
+
return info;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index da1a557..32ed102 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -23,6 +23,7 @@
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -33,8 +34,12 @@
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
/**
* Expanded view of a bubble when it's part of the bubble bar.
*
@@ -47,6 +52,9 @@
private BubbleController mController;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
+ private BubbleBarMenuViewController mMenuViewController;
+ private @Nullable Supplier<Rect> mLayerBoundsSupplier;
+ private @Nullable Consumer<String> mUnBubbleConversationCallback;
private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
private @Nullable TaskView mTaskView;
@@ -98,6 +106,13 @@
});
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Hide manage menu when view disappears
+ mMenuViewController.hideMenu(false /* animated */);
+ }
+
/** Set the BubbleController on the view, must be called before doing anything else. */
public void initialize(BubbleController controller) {
mController = controller;
@@ -108,6 +123,36 @@
addView(mTaskView);
mTaskView.setEnableSurfaceClipping(true);
mTaskView.setCornerRadius(mCornerRadius);
+ mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+ mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
+ @Override
+ public void onMenuVisibilityChanged(boolean visible) {
+ if (mTaskView == null || mLayerBoundsSupplier == null) return;
+ // Updates the obscured touchable region for the task surface.
+ mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
+ }
+
+ @Override
+ public void onUnBubbleConversation(Bubble bubble) {
+ if (mUnBubbleConversationCallback != null) {
+ mUnBubbleConversationCallback.accept(bubble.getKey());
+ }
+ }
+
+ @Override
+ public void onOpenAppSettings(Bubble bubble) {
+ mController.collapseStack();
+ mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+ }
+
+ @Override
+ public void onDismissBubble(Bubble bubble) {
+ mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+ }
+ });
+ mHandleView.setOnClickListener(view -> {
+ mMenuViewController.showMenu(true /* animated */);
+ });
}
// TODO (b/275087636): call this when theme/config changes
@@ -183,11 +228,13 @@
}
mBubbleTaskViewHelper.cleanUpTaskView();
}
+ mMenuViewController.hideMenu(false /* animated */);
}
- /** Updates the bubble shown in this task view. */
+ /** Updates the bubble shown in the expanded view. */
public void update(Bubble bubble) {
mBubbleTaskViewHelper.update(bubble);
+ mMenuViewController.updateMenu(bubble);
}
/** The task id of the activity shown in the task view, if it exists. */
@@ -195,6 +242,17 @@
return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
}
+ /** Sets layer bounds supplier used for obscured touchable region of task view */
+ void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
+ mLayerBoundsSupplier = supplier;
+ }
+
+ /** Sets the function to call to un-bubble the given conversation. */
+ public void setUnBubbleConversationCallback(
+ @Nullable Consumer<String> unBubbleConversationCallback) {
+ mUnBubbleConversationCallback = unBubbleConversationCallback;
+ }
+
/**
* Call when the location or size of the view has changed to update TaskView.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index e121aa4..ce26bc0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -20,7 +20,6 @@
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -38,35 +37,35 @@
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
- private final int mHandleWidth;
- private final int mHandleHeight;
- private final @ColorInt int mHandleLightColor;
- private final @ColorInt int mHandleDarkColor;
+ private int mHandleWidth;
+ private int mHandleHeight;
+ private @ColorInt int mHandleLightColor;
+ private @ColorInt int mHandleDarkColor;
private @Nullable ObjectAnimator mColorChangeAnim;
public BubbleBarHandleView(Context context) {
- this(context, null);
+ this(context, null /* attrs */);
}
public BubbleBarHandleView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, 0 /* defStyleAttr */);
}
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
}
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- Resources resources = context.getResources();
- mHandleWidth = resources.getDimensionPixelSize(
+
+ mHandleWidth = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_handle_width);
- mHandleHeight = resources.getDimensionPixelSize(
+ mHandleHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_handle_height);
- mHandleLightColor = ContextCompat.getColor(context,
+ mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
- mHandleDarkColor = ContextCompat.getColor(context,
+ mHandleDarkColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_dark);
setClipToOutline(true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b1a725b..bc622e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -32,6 +32,8 @@
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
+import java.util.function.Consumer;
+
/**
* Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
* manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -53,6 +55,7 @@
@Nullable
private BubbleViewProvider mExpandedBubble;
private BubbleBarExpandedView mExpandedView;
+ private @Nullable Consumer<String> mUnBubbleConversationCallback;
// TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
/** Whether the expanded view is displaying on the left of the screen or not. */
@@ -146,6 +149,13 @@
final int width = mPositioner.getExpandedViewWidthForBubbleBar();
final int height = mPositioner.getExpandedViewHeightForBubbleBar();
mExpandedView.setVisibility(GONE);
+ mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
+ mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
+ mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
+ if (mUnBubbleConversationCallback != null) {
+ mUnBubbleConversationCallback.accept(bubbleKey);
+ }
+ });
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
}
@@ -165,6 +175,12 @@
showScrim(false);
}
+ /** Sets the function to call to un-bubble the given conversation. */
+ public void setUnBubbleConversationCallback(
+ @Nullable Consumer<String> unBubbleConversationCallback) {
+ mUnBubbleConversationCallback = unBubbleConversationCallback;
+ }
+
/** Updates the expanded view size and position. */
private void updateExpandedView() {
if (mExpandedView == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
new file mode 100644
index 0000000..00b9777
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Bubble bar expanded view menu item view to display menu action details
+ */
+public class BubbleBarMenuItemView extends LinearLayout {
+ private ImageView mImageView;
+ private TextView mTextView;
+
+ public BubbleBarMenuItemView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+ }
+
+ public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
+ mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+ }
+
+ /**
+ * Update menu item with the details and tint color
+ */
+ void update(Icon icon, String title, @ColorInt int tint) {
+ if (tint == Color.TRANSPARENT) {
+ final TypedArray typedArray = getContext().obtainStyledAttributes(
+ new int[]{android.R.attr.textColorPrimary});
+ mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
+ } else {
+ icon.setTint(tint);
+ mTextView.setTextColor(tint);
+ }
+
+ mImageView.setImageIcon(icon);
+ mTextView.setText(title);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
new file mode 100644
index 0000000..211fe0d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Bubble bar expanded view menu
+ */
+public class BubbleBarMenuView extends LinearLayout {
+ private ViewGroup mBubbleSectionView;
+ private ViewGroup mActionsSectionView;
+ private ImageView mBubbleIconView;
+ private TextView mBubbleTitleView;
+
+ public BubbleBarMenuView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+ }
+
+ public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
+ mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
+ mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
+ mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+ }
+
+ /** Update menu details with bubble info */
+ void updateInfo(Bubble bubble) {
+ if (bubble.getIcon() != null) {
+ mBubbleIconView.setImageIcon(bubble.getIcon());
+ } else {
+ mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
+ }
+ mBubbleTitleView.setText(bubble.getTitle());
+ }
+
+ /**
+ * Update menu action items views
+ * @param actions used to populate menu item views
+ */
+ void updateActions(ArrayList<MenuAction> actions) {
+ mActionsSectionView.removeAllViews();
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ for (MenuAction action : actions) {
+ BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
+ R.layout.bubble_bar_menu_item, mActionsSectionView, false);
+ itemView.update(action.mIcon, action.mTitle, action.mTint);
+ itemView.setOnClickListener(action.mOnClick);
+ mActionsSectionView.addView(itemView);
+ }
+ }
+
+ /** Sets on close menu listener */
+ void setOnCloseListener(Runnable onClose) {
+ mBubbleSectionView.setOnClickListener(view -> {
+ onClose.run();
+ });
+ }
+
+ /**
+ * Overridden to proxy to section views alpha.
+ * @implNote
+ * If animate alpha on the parent (menu container) view, section view shadows get distorted.
+ * To prevent distortion and artifacts alpha changes applied directly on the section views.
+ */
+ @Override
+ public void setAlpha(float alpha) {
+ mBubbleSectionView.setAlpha(alpha);
+ mActionsSectionView.setAlpha(alpha);
+ }
+
+ /**
+ * Overridden to proxy section view alpha value.
+ * @implNote
+ * The assumption is that both section views have the same alpha value
+ */
+ @Override
+ public float getAlpha() {
+ return mBubbleSectionView.getAlpha();
+ }
+
+ /**
+ * Menu action details used to create menu items
+ */
+ static class MenuAction {
+ private Icon mIcon;
+ private @ColorInt int mTint;
+ private String mTitle;
+ private OnClickListener mOnClick;
+
+ MenuAction(Icon icon, String title, OnClickListener onClick) {
+ this(icon, title, Color.TRANSPARENT, onClick);
+ }
+
+ MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
+ this.mIcon = icon;
+ this.mTitle = title;
+ this.mTint = tint;
+ this.mOnClick = onClick;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
new file mode 100644
index 0000000..8be140c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 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.bubbles.bar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Manages bubble bar expanded view menu presentation and animations
+ */
+class BubbleBarMenuViewController {
+ private static final float MENU_INITIAL_SCALE = 0.5f;
+ private final Context mContext;
+ private final ViewGroup mRootView;
+ private @Nullable Listener mListener;
+ private @Nullable Bubble mBubble;
+ private @Nullable BubbleBarMenuView mMenuView;
+ /** A transparent view used to intercept touches to collapse menu when presented */
+ private @Nullable View mScrimView;
+ private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
+ private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+
+ BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+ mContext = context;
+ mRootView = rootView;
+ mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ }
+
+ /** Sets menu actions listener */
+ void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
+ /** Update menu with bubble */
+ void updateMenu(@NonNull Bubble bubble) {
+ mBubble = bubble;
+ }
+
+ /**
+ * Show bubble bar expanded view menu
+ * @param animated if should animate transition
+ */
+ void showMenu(boolean animated) {
+ if (mMenuView == null || mScrimView == null) {
+ setupMenu();
+ }
+ cancelAnimations();
+ mMenuView.setVisibility(View.VISIBLE);
+ mScrimView.setVisibility(View.VISIBLE);
+ Runnable endActions = () -> {
+ mMenuView.getChildAt(0).requestAccessibilityFocus();
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(true /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(true /* show */, endActions);
+ } else {
+ endActions.run();
+ }
+ }
+
+ /**
+ * Hide bubble bar expanded view menu
+ * @param animated if should animate transition
+ */
+ void hideMenu(boolean animated) {
+ if (mMenuView == null || mScrimView == null) return;
+ cancelAnimations();
+ Runnable endActions = () -> {
+ mMenuView.setVisibility(View.GONE);
+ mScrimView.setVisibility(View.GONE);
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(false /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(false /* show */, endActions);
+ } else {
+ endActions.run();
+ }
+ }
+
+ /**
+ * Animate show/hide menu transition
+ * @param show if should show or hide the menu
+ * @param endActions will be called when animation ends
+ */
+ private void animateTransition(boolean show, Runnable endActions) {
+ if (mMenuView == null) return;
+ mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
+ mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
+ mMenuAnimator
+ .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
+ .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
+ .withEndActions(() -> {
+ mMenuAnimator = null;
+ endActions.run();
+ })
+ .start();
+ }
+
+ /** Cancel running animations */
+ private void cancelAnimations() {
+ if (mMenuAnimator != null) {
+ mMenuAnimator.cancel();
+ mMenuAnimator = null;
+ }
+ }
+
+ /** Sets up and inflate menu views */
+ private void setupMenu() {
+ // Menu view setup
+ mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
+ R.layout.bubble_bar_menu_view, mRootView, false);
+ mMenuView.setAlpha(0f);
+ mMenuView.setPivotY(0f);
+ mMenuView.setScaleY(MENU_INITIAL_SCALE);
+ mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */));
+ if (mBubble != null) {
+ mMenuView.updateInfo(mBubble);
+ mMenuView.updateActions(createMenuActions(mBubble));
+ }
+ // Scrim view setup
+ mScrimView = new View(mContext);
+ mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mScrimView.setOnClickListener(view -> hideMenu(true /* animated */));
+ // Attach to root view
+ mRootView.addView(mScrimView);
+ mRootView.addView(mMenuView);
+ }
+
+ /**
+ * Creates menu actions to populate menu view
+ * @param bubble used to create actions depending on bubble type
+ */
+ private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
+ ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
+ Resources resources = mContext.getResources();
+
+ if (bubble.isConversation()) {
+ // Don't bubble conversation action
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
+ resources.getString(R.string.bubbles_dont_bubble_conversation),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onUnBubbleConversation(bubble);
+ }
+ }
+ ));
+ // Open settings action
+ Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
+ bubble.getRawAppBadge()) : null;
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ appIcon,
+ resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onOpenAppSettings(bubble);
+ }
+ }
+ ));
+ }
+
+ // Dismiss bubble action
+ menuActions.add(new BubbleBarMenuView.MenuAction(
+ Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
+ resources.getString(R.string.bubble_dismiss_text),
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+ view -> {
+ hideMenu(true /* animated */);
+ if (mListener != null) {
+ mListener.onDismissBubble(bubble);
+ }
+ }
+ ));
+
+ return menuActions;
+ }
+
+ /**
+ * Bubble bar expanded view menu actions listener
+ */
+ interface Listener {
+ /**
+ * Called when manage menu is shown/hidden
+ * If animated will be called when animation ends
+ */
+ void onMenuVisibilityChanged(boolean visible);
+
+ /**
+ * Un-bubbles conversation and removes the bubble from the stack
+ * This conversation will not be bubbled with new messages
+ * @see com.android.wm.shell.bubbles.BubbleController
+ */
+ void onUnBubbleConversation(Bubble bubble);
+
+ /**
+ * Launches app notification bubble settings for the bubble with intent created in:
+ * {@code Bubble.getSettingsIntent}
+ */
+ void onOpenAppSettings(Bubble bubble);
+
+ /**
+ * Dismiss bubble and remove it from the bubble stack
+ */
+ void onDismissBubble(Bubble bubble);
+ }
+}
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 bc0b71c..2dbccac 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
@@ -542,9 +542,12 @@
Optional<PipTouchHandler> pipTouchHandlerOptional,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
KeyguardTransitionHandler keyguardTransitionHandler,
+ Optional<DesktopModeController> desktopModeController,
+ Optional<DesktopTasksController> desktopTasksController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler);
+ pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
+ desktopModeController, desktopTasksController);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 1169af9..db6c258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -33,6 +33,7 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Region;
import android.net.Uri;
@@ -414,6 +415,25 @@
}
/**
+ * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+ * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+ * animating.
+ */
+ public void syncSurfaceState(@NonNull TransitionInfo info,
+ SurfaceControl.Transaction finishTransaction) {
+ // Add rounded corners to freeform windows
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.dialogCornerRadius});
+ final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+ ta.recycle();
+ for (TransitionInfo.Change change: info.getChanges()) {
+ if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
+ }
+ }
+ }
+
+ /**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
private final class SettingsObserver extends ContentObserver {
@@ -500,6 +520,11 @@
}
@Override
+ public void showDesktopApp(int taskId) throws RemoteException {
+ // TODO
+ }
+
+ @Override
public int getVisibleTaskCount(int displayId) throws RemoteException {
int[] result = new int[1];
executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
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 b551e6b..d191fcd 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.R
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -24,6 +25,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.content.res.TypedArray
import android.graphics.Point
import android.graphics.Rect
import android.graphics.Region
@@ -277,6 +279,11 @@
}
/** Move a task to the front */
+ fun moveTaskToFront(taskId: Int) {
+ shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+ }
+
+ /** Move a task to the front */
fun moveTaskToFront(taskInfo: RunningTaskInfo) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
@@ -460,6 +467,25 @@
}
}
+ /**
+ * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+ * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+ * animating.
+ */
+ fun syncSurfaceState(
+ info: TransitionInfo,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // Add rounded corners to freeform windows
+ val ta: TypedArray = context.obtainStyledAttributes(
+ intArrayOf(R.attr.dialogCornerRadius))
+ val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
+ ta.recycle()
+ info.changes
+ .filter { it.taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+ .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+ }
+
private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
@@ -774,6 +800,13 @@
) { c -> c.hideStashedDesktopApps(displayId) }
}
+ override fun showDesktopApp(taskId: Int) {
+ ExecutorUtils.executeRemoteCallWithTaskPermission(
+ controller,
+ "showDesktopApp"
+ ) { c -> c.moveTaskToFront(taskId) }
+ }
+
override fun getVisibleTaskCount(displayId: Int): Int {
val result = IntArray(1)
ExecutorUtils.executeRemoteCallWithTaskPermission(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 05a6e33..ee3a080 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -32,6 +32,9 @@
/** Hide apps that may be stashed */
void hideStashedDesktopApps(int displayId);
+ /** Bring task with the given id to front */
+ oneway void showDesktopApp(int taskId);
+
/** Get count of visible desktop tasks on the given display */
int getVisibleTaskCount(int displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 658359e..c25352b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -166,7 +166,7 @@
"unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
- Log.wtf(TAG, "Failed to play: " + info);
+ Log.w(TAG, "Failed to play: " + info);
return false;
}
}
@@ -186,6 +186,7 @@
public void onTransitionFinished(
WindowContainerTransaction wct, SurfaceControl.Transaction sct) {
mMainExecutor.execute(() -> {
+ mStartedTransitions.remove(transition);
finishCallback.onTransitionFinished(wct, null);
});
}
@@ -206,7 +207,7 @@
final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"unknown keyguard transition %s", currentTransition);
return;
}
@@ -217,14 +218,17 @@
// the device sleeping/waking, so it's best to ignore this and keep playing anyway.
return;
} else {
- finishAnimationImmediately(currentTransition);
+ finishAnimationImmediately(currentTransition, playing);
}
}
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted,
SurfaceControl.Transaction finishTransaction) {
- finishAnimationImmediately(transition);
+ final IRemoteTransition playing = mStartedTransitions.remove(transition);
+ if (playing != null) {
+ finishAnimationImmediately(transition, playing);
+ }
}
@Nullable
@@ -234,21 +238,17 @@
return null;
}
- private void finishAnimationImmediately(IBinder transition) {
- final IRemoteTransition playing = mStartedTransitions.get(transition);
-
- if (playing != null) {
- final IBinder fakeTransition = new Binder();
- final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
- final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
- final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
- try {
- playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
- } catch (RemoteException e) {
- // There is no good reason for this to happen because the player is a local object
- // implementing an AIDL interface.
- Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
- }
+ private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) {
+ final IBinder fakeTransition = new Binder();
+ final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
+ final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
+ final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
+ try {
+ playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+ } catch (RemoteException e) {
+ // There is no good reason for this to happen because the player is a local object
+ // implementing an AIDL interface.
+ Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d2b0e28..9863099 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -348,8 +348,6 @@
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionConsumedCallback consumedCallback,
- @Nullable TransitionFinishedCallback finishedCallback,
int extraTransitType, boolean resizeAnim) {
if (mPendingEnter != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
@@ -357,20 +355,16 @@
return null;
}
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback,
- extraTransitType, resizeAnim);
+ setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
@Nullable RemoteTransition remoteTransition,
- @Nullable TransitionConsumedCallback consumedCallback,
- @Nullable TransitionFinishedCallback finishedCallback,
int extraTransitType, boolean resizeAnim) {
mPendingEnter = new EnterSession(
- transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType,
- resizeAnim);
+ transition, remoteTransition, extraTransitType, resizeAnim);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -608,12 +602,10 @@
final boolean mResizeAnim;
EnterSession(IBinder transition,
- @Nullable TransitionConsumedCallback consumedCallback,
- @Nullable TransitionFinishedCallback finishedCallback,
@Nullable RemoteTransition remoteTransition,
int extraTransitType, boolean resizeAnim) {
- super(transition, consumedCallback, finishedCallback, remoteTransition,
- extraTransitType);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */,
+ remoteTransition, extraTransitType);
this.mResizeAnim = resizeAnim;
}
}
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 256d48c..b2526ee 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
@@ -396,7 +396,7 @@
prepareEnterSplitScreen(wct, task, stagePosition);
if (ENABLE_SHELL_TRANSITIONS) {
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
- null, this, null /* consumedCallback */, null /* finishedCallback */,
+ null, this,
isSplitScreenVisible()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
!mIsDropEntering);
@@ -507,8 +507,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- null /* consumedCallback */, null /* finishedCallback */, extraTransitType,
- !mIsDropEntering);
+ extraTransitType, !mIsDropEntering);
}
/** Launches an activity into split by legacy transition. */
@@ -665,8 +664,7 @@
if (mPausingTasks.contains(mainTaskId)) {
mPausingTasks.clear();
}
- mSplitTransitions.startEnterTransition(
- TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -716,8 +714,7 @@
wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2);
}
- mSplitTransitions.startEnterTransition(
- TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -1453,8 +1450,6 @@
if (!mMainStage.isActive()) return;
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
- wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
- false /* reparentLeafTaskIfRelaunch */);
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -2295,14 +2290,28 @@
out = new WindowContainerTransaction();
final StageTaskListener stage = getStageOfTask(triggerTask);
if (stage != null) {
- // Dismiss split if the last task in one of the stages is going away
if (isClosingType(type) && stage.getChildCount() == 1) {
+ // Dismiss split if the last task in one of the stages is going away
// The top should be the opposite side that is closing:
- int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
- : STAGE_TYPE_MAIN;
+ int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
prepareExitSplitScreen(dismissTop, out);
mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
+ } else if (isOpening && !mPausingTasks.isEmpty()) {
+ // One of the splitting task is opening while animating the split pair in
+ // recents, which means to dismiss the split pair to this task.
+ int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(dismissTop, out);
+ mSplitTransitions.setDismissTransition(transition, dismissTop,
+ EXIT_REASON_APP_FINISHED);
+ } else if (!isSplitScreenVisible() && isOpening) {
+ // If split running backgroud and trigger task is appearing into split,
+ // prepare to enter split screen.
+ prepareEnterSplitScreen(out);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
}
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
@@ -2327,8 +2336,7 @@
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- null /* consumedCallback */, null /* finishedCallback */,
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, !mIsDropEntering);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
}
}
return out;
@@ -2586,6 +2594,7 @@
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2594,12 +2603,16 @@
continue;
}
final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
- if (stageType == STAGE_TYPE_MAIN
+ if (mainChild == null && stageType == STAGE_TYPE_MAIN
&& (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) {
// Includes TRANSIT_CHANGE to cover reparenting top-most task to split.
mainChild = change;
- } else if (stageType == STAGE_TYPE_SIDE && isOpeningType(change.getMode())) {
+ } else if (sideChild == null && stageType == STAGE_TYPE_SIDE
+ && isOpeningType(change.getMode())) {
sideChild = change;
+ } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) {
+ // Collect all to back task's and evict them when transition finished.
+ evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
@@ -2661,10 +2674,14 @@
mSideStage.evictInvisibleChildren(callbackWct);
}
}
+ if (!evictWct.isEmpty()) {
+ callbackWct.merge(evictWct, true);
+ }
if (enterTransition.mResizeAnim) {
mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
+ callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
mPausingTasks.clear();
});
@@ -2815,6 +2832,7 @@
dismissTransition.setFinishedCallback((callbackWct, callbackT) -> {
mMainStage.getSplitDecorManager().release(callbackT);
mSideStage.getSplitDecorManager().release(callbackT);
+ callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
});
addDividerBarToTransition(info, false /* show */);
@@ -2868,8 +2886,7 @@
}
setSplitsVisible(false);
- finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
- true /* reparentLeafTaskIfRelaunch */);
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
logExit(EXIT_REASON_UNKNOWN);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 64571e0..5ee5324 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,6 +40,9 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -65,6 +68,8 @@
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private final KeyguardTransitionHandler mKeyguardHandler;
+ private DesktopModeController mDesktopModeController;
+ private DesktopTasksController mDesktopTasksController;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -81,6 +86,9 @@
/** Keyguard exit/occlude/unocclude transition. */
static final int TYPE_KEYGUARD = 5;
+ /** Recents Transition while in desktop mode. */
+ static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -132,7 +140,9 @@
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
- KeyguardTransitionHandler keyguardHandler) {
+ KeyguardTransitionHandler keyguardHandler,
+ Optional<DesktopModeController> desktopModeControllerOptional,
+ Optional<DesktopTasksController> desktopTasksControllerOptional) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
@@ -149,6 +159,8 @@
if (mRecentsHandler != null) {
mRecentsHandler.addMixer(this);
}
+ mDesktopModeController = desktopModeControllerOptional.orElse(null);
+ mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
}, this);
}
}
@@ -218,7 +230,8 @@
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
+ if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
+ || DesktopModeStatus.isActive(mPlayer.getContext()))) {
return this;
}
return null;
@@ -233,6 +246,13 @@
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = mRecentsHandler;
mActiveTransitions.add(mixed);
+ } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "desktop mode is active, so treat it as Mixed.");
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+ mixed.mLeftoversHandler = mRecentsHandler;
+ mActiveTransitions.add(mixed);
} else {
throw new IllegalStateException("Accepted a recents transition but don't know how to"
+ " handle it");
@@ -306,6 +326,9 @@
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
return animateKeyguard(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -582,6 +605,30 @@
return true;
}
+ private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean consumed = mRecentsHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+ if (!consumed) {
+ return false;
+ }
+ //Sync desktop mode state (proto 1)
+ if (mDesktopModeController != null) {
+ mDesktopModeController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+ //Sync desktop mode state (proto 2)
+ if (mDesktopTasksController != null) {
+ mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+
+ return false;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -625,6 +672,9 @@
finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -650,6 +700,8 @@
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dc8a258..4c678a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -327,6 +327,7 @@
final int wallpaperTransit = getWallpaperTransitType(info);
boolean isDisplayRotationAnimationStarted = false;
final boolean isDreamTransition = isDreamTransition(info);
+ final boolean isOnlyTranslucent = isOnlyTranslucent(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -452,6 +453,17 @@
final int layer = zSplitLine + numChanges - i;
startTransaction.setLayer(change.getLeash(), layer);
}
+ } else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
+ && TransitionUtil.isClosingType(mode)) {
+ // If there is a closing translucent task in an OPENING transition, we will
+ // actually select a CLOSING animation, so move the closing task into
+ // the animating part of the z-order.
+
+ // See Transitions#setupAnimHierarchy for details about these variables.
+ final int numChanges = info.getChanges().size();
+ final int zSplitLine = numChanges + 1;
+ final int layer = zSplitLine + numChanges - i;
+ startTransaction.setLayer(change.getLeash(), layer);
}
}
@@ -543,6 +555,29 @@
return false;
}
+ /**
+ * Does `info` only contain translucent visibility changes (CHANGEs are ignored). We select
+ * different animations and z-orders for these
+ */
+ private static boolean isOnlyTranslucent(@NonNull TransitionInfo info) {
+ int translucentOpen = 0;
+ int translucentClose = 0;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getMode() == TRANSIT_CHANGE) continue;
+ if (change.hasFlags(FLAG_TRANSLUCENT)) {
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ translucentOpen += 1;
+ } else {
+ translucentClose += 1;
+ }
+ } else {
+ return false;
+ }
+ }
+ return (translucentOpen + translucentClose) > 0;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index fc301b6..d978eaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -103,10 +103,11 @@
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
// TransitionAnimation loads appropriate animation later.
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
- translucent = true;
- }
- if (isTask && !translucent) {
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ if (isTask && translucent && !enter) {
+ // For closing translucent tasks, use the activity close animation
+ animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+ } else if (isTask && !translucent) {
animAttr = enter
? R.styleable.WindowAnimation_taskOpenEnterAnimation
: R.styleable.WindowAnimation_taskOpenExitAnimation;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 3b306e7..ce8d792 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -16,9 +16,12 @@
package com.android.wm.shell.transition;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -567,8 +570,8 @@
layer = zSplitLine + numChanges - i;
}
} else { // CHANGE or other
- if (isClosing) {
- // Put below CLOSE mode.
+ if (isClosing || TransitionUtil.isOrderOnly(change)) {
+ // Put below CLOSE mode (in the "static" section).
layer = zSplitLine - i;
} else {
// Put above CLOSE mode.
@@ -1081,6 +1084,16 @@
}
}
}
+ if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // This freeform task is on top of keyguard, so its windowing mode should be changed to
+ // fullscreen.
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ wct.setWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_FULLSCREEN);
+ wct.setBounds(request.getTriggerTask().token, null);
+ }
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
// Currently, WMCore only does one transition at a time. If it makes a requestStart, it
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 ac5ff20..67ec6cf 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
@@ -248,6 +248,9 @@
mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ wct.addInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+ mCaptionInsetsRect);
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -345,6 +348,8 @@
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
wct.removeInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.captionBar());
+ wct.removeInsetsSource(mTaskInfo.token,
+ mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 78a2a38..e382a0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -70,6 +70,7 @@
],
data: [
":FlickerTestApp",
+ "trace_config/*",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index 8818aa2..ffe6d6a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -16,6 +16,9 @@
-->
<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
<option name="test-tag" value="FlickerTests" />
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on" />
@@ -41,22 +44,53 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="run-command" value="settings put system show_touches 1" />
<option name="run-command" value="settings put system pointer_location 1" />
- <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard" />
<option name="teardown-command" value="settings delete system show_touches" />
<option name="teardown-command" value="settings delete system pointer_location" />
- <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="{PACKAGE}"/>
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6000s" />
<option name="hidden-api-checks" value="false" />
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true" />
</test>
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+ <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+ <option name="collect-on-run-ended-only" value="false" />
+ <option name="clean-up" value="true" />
+ </metrics_collector>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
new file mode 100644
index 0000000..406ada9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.android.wm.shell.flicker"
+ atrace_apps: "com.android.wm.shell.flicker.other"
+ atrace_apps: "com.android.wm.shell.flicker.bubbles"
+ atrace_apps: "com.android.wm.shell.flicker.pip"
+ atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+
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 9ce18db..e3dd449 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
@@ -345,6 +345,18 @@
}
@Test
+ fun moveTaskToFront_postsWctWithReorderOp() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+
+ controller.moveTaskToFront(task1)
+
+ val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, task1)
+ }
+
+ @Test
fun moveToNextDisplay_noOtherDisplays() {
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 5f705d7..3b05651 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -185,7 +185,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null,
+ new RemoteTransition(testRemote, "Test"), mStageCoordinator,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
@@ -412,7 +412,7 @@
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(new TestRemoteTransition(), "Test"),
- mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
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 5a2326b..f941e95 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
@@ -316,7 +316,8 @@
releaseOrder.verify(t).remove(captionContainerSurface);
releaseOrder.verify(t).remove(decorContainerSurface);
releaseOrder.verify(t).apply();
- verify(mMockWindowContainerTransaction)
+ // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
+ verify(mMockWindowContainerTransaction, Mockito.times(2))
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7af6efb..06aed63 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,7 +17,6 @@
#include "Properties.h"
#include "Debug.h"
-#include "log/log_main.h"
#ifdef __ANDROID__
#include "HWUIProperties.sysprop.h"
#endif
@@ -220,12 +219,15 @@
return sRenderPipelineType;
}
-void Properties::overrideRenderPipelineType(RenderPipelineType type, bool inUnitTest) {
+void Properties::overrideRenderPipelineType(RenderPipelineType type) {
// If we're doing actual rendering then we can't change the renderer after it's been set.
- // Unit tests can freely change this as often as it wants.
- LOG_ALWAYS_FATAL_IF(sRenderPipelineType != RenderPipelineType::NotInitialized &&
- sRenderPipelineType != type && !inUnitTest,
- "Trying to change pipeline but it's already set.");
+ // Unit tests can freely change this as often as it wants, though, as there's no actual
+ // GL rendering happening
+ if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
+ LOG_ALWAYS_FATAL_IF(sRenderPipelineType != type,
+ "Trying to change pipeline but it's already set");
+ return;
+ }
sRenderPipelineType = type;
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 24e206b..bb47744 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -303,7 +303,7 @@
static bool enableRTAnimations;
// Used for testing only to change the render pipeline.
- static void overrideRenderPipelineType(RenderPipelineType, bool inUnitTest = false);
+ static void overrideRenderPipelineType(RenderPipelineType);
static bool runningInEmulator;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 4cffc6c..d4e919f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -23,13 +23,11 @@
#include <GrDirectContext.h>
#include <GrTypes.h>
#include <android/sync.h>
+#include <gui/TraceUtils.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
-#include <cstring>
-
-#include <gui/TraceUtils.h>
#include "Properties.h"
#include "RenderThread.h"
#include "pipeline/skia/ShaderCache.h"
@@ -90,19 +88,6 @@
}
}
-GrVkGetProc VulkanManager::sSkiaGetProp = [](const char* proc_name, VkInstance instance,
- VkDevice device) {
- if (device != VK_NULL_HANDLE) {
- if (strcmp("vkQueueSubmit", proc_name) == 0) {
- return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueSubmit;
- } else if (strcmp("vkQueueWaitIdle", proc_name) == 0) {
- return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueWaitIdle;
- }
- return vkGetDeviceProcAddr(device, proc_name);
- }
- return vkGetInstanceProcAddr(instance, proc_name);
-};
-
#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -138,6 +123,7 @@
}
mGraphicsQueue = VK_NULL_HANDLE;
+ mAHBUploadQueue = VK_NULL_HANDLE;
mDevice = VK_NULL_HANDLE;
mPhysicalDevice = VK_NULL_HANDLE;
mInstance = VK_NULL_HANDLE;
@@ -231,7 +217,7 @@
mDriverVersion = physDeviceProperties.driverVersion;
// query to get the initial queue props size
- uint32_t queueCount;
+ uint32_t queueCount = 0;
mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr);
LOG_ALWAYS_FATAL_IF(!queueCount);
@@ -239,11 +225,14 @@
std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]);
mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get());
+ constexpr auto kRequestedQueueCount = 2;
+
// iterate to find the graphics queue
mGraphicsQueueIndex = queueCount;
for (uint32_t i = 0; i < queueCount; i++) {
if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
mGraphicsQueueIndex = i;
+ LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < kRequestedQueueCount);
break;
}
}
@@ -273,7 +262,14 @@
LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension);
}
- grExtensions.init(sSkiaGetProp, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+ auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+ if (device != VK_NULL_HANDLE) {
+ return vkGetDeviceProcAddr(device, proc_name);
+ }
+ return vkGetInstanceProcAddr(instance, proc_name);
+ };
+
+ grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
mInstanceExtensions.data(), mDeviceExtensions.size(),
mDeviceExtensions.data());
@@ -312,7 +308,7 @@
// and we can't depend on it on all platforms
features.features.robustBufferAccess = VK_FALSE;
- float queuePriorities[1] = {0.0};
+ float queuePriorities[kRequestedQueueCount] = {0.0};
void* queueNextPtr = nullptr;
@@ -345,7 +341,7 @@
queueNextPtr, // pNext
0, // VkDeviceQueueCreateFlags
mGraphicsQueueIndex, // queueFamilyIndex
- 1, // queueCount
+ kRequestedQueueCount, // queueCount
queuePriorities, // pQueuePriorities
};
@@ -403,6 +399,7 @@
this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
if (Properties::enablePartialUpdates && Properties::useBufferAge) {
mSwapBehavior = SwapBehavior::BufferAge;
@@ -416,16 +413,24 @@
sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
ContextType contextType) {
+ auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+ if (device != VK_NULL_HANDLE) {
+ return vkGetDeviceProcAddr(device, proc_name);
+ }
+ return vkGetInstanceProcAddr(instance, proc_name);
+ };
+
GrVkBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
backendContext.fDevice = mDevice;
- backendContext.fQueue = mGraphicsQueue;
+ backendContext.fQueue =
+ (contextType == ContextType::kRenderThread) ? mGraphicsQueue : mAHBUploadQueue;
backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
backendContext.fMaxAPIVersion = mAPIVersion;
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
- backendContext.fGetProc = sSkiaGetProp;
+ backendContext.fGetProc = std::move(getProc);
LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
this->incStrong((void*)onGrContextReleased);
@@ -636,8 +641,6 @@
ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
} else {
ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
-
- std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
mQueueWaitIdle(mGraphicsQueue);
}
if (mDestroySemaphoreContext) {
@@ -652,7 +655,6 @@
void VulkanManager::destroySurface(VulkanSurface* surface) {
// Make sure all submit commands have finished before starting to destroy objects.
if (VK_NULL_HANDLE != mGraphicsQueue) {
- std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
mQueueWaitIdle(mGraphicsQueue);
}
mDeviceWaitIdle(mDevice);
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 00a40c0..2be1ffd 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -17,10 +17,6 @@
#ifndef VULKANMANAGER_H
#define VULKANMANAGER_H
-#include <functional>
-#include <mutex>
-
-#include "vulkan/vulkan_core.h"
#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
#define VK_USE_PLATFORM_ANDROID_KHR
#endif
@@ -186,25 +182,8 @@
VkDevice mDevice = VK_NULL_HANDLE;
uint32_t mGraphicsQueueIndex;
-
- std::mutex mGraphicsQueueMutex;
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
-
- static VKAPI_ATTR VkResult interceptedVkQueueSubmit(VkQueue queue, uint32_t submitCount,
- const VkSubmitInfo* pSubmits,
- VkFence fence) {
- sp<VulkanManager> manager = VulkanManager::getInstance();
- std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
- return manager->mQueueSubmit(queue, submitCount, pSubmits, fence);
- }
-
- static VKAPI_ATTR VkResult interceptedVkQueueWaitIdle(VkQueue queue) {
- sp<VulkanManager> manager = VulkanManager::getInstance();
- std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
- return manager->mQueueWaitIdle(queue);
- }
-
- static GrVkGetProc sSkiaGetProp;
+ VkQueue mAHBUploadQueue = VK_NULL_HANDLE;
// Variables saved to populate VkFunctorInitParams.
static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0);
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 9d5c13e..81ecfe5 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -61,12 +61,12 @@
ADD_FAILURE() << "ClipState not a rect"; \
}
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
- TEST(test_case_name, test_name##_##pipeline) { \
- RenderPipelineType oldType = Properties::getRenderPipelineType(); \
- Properties::overrideRenderPipelineType(RenderPipelineType::pipeline, true); \
- functionCall; \
- Properties::overrideRenderPipelineType(oldType, true); \
+#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
+ TEST(test_case_name, test_name##_##pipeline) { \
+ RenderPipelineType oldType = Properties::getRenderPipelineType(); \
+ Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
+ functionCall; \
+ Properties::overrideRenderPipelineType(oldType); \
};
#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
@@ -78,27 +78,29 @@
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
* (for e.g. accessing its RenderState)
*/
-#define RENDERTHREAD_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
+ /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
renderthread::RenderThread& renderThread)
/**
* Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
*/
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
+#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
+ /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
renderthread::RenderThread& renderThread)
/**
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 11cb2be..1e3c154 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -81,6 +81,18 @@
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
/**
+ * A category indicating that the associated provider is only intended for use within the app
+ * that hosts the provider.
+ *
+ * <p>Declaring this category helps the system save resources by avoiding the launch of services
+ * whose routes are known to be private to the app that provides them.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_SELF_SCAN_ONLY =
+ "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY";
+
+ /**
* The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)}
* when {@link MediaRoute2ProviderService} created a session although there was no creation
* request.
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index ee1f203..3935de8 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -440,14 +440,14 @@
* <p>If this method returns {@link #SUBTEXT_CUSTOM}, then the subtext is obtained form
* {@link #getCustomSubtextMessage()}.
*
- * @see #SUBTEXT_NONE,
- * @see #SUBTEXT_ERROR_UNKNOWN,
- * @see #SUBTEXT_SUBSCRIPTION_REQUIRED,
- * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED,
- * @see #SUBTEXT_AD_ROUTING_DISALLOWED,
- * @see #SUBTEXT_DEVICE_LOW_POWER,
- * @see #SUBTEXT_UNAUTHORIZED ,
- * @see #SUBTEXT_TRACK_UNSUPPORTED,
+ * @see #SUBTEXT_NONE
+ * @see #SUBTEXT_ERROR_UNKNOWN
+ * @see #SUBTEXT_SUBSCRIPTION_REQUIRED
+ * @see #SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED
+ * @see #SUBTEXT_AD_ROUTING_DISALLOWED
+ * @see #SUBTEXT_DEVICE_LOW_POWER
+ * @see #SUBTEXT_UNAUTHORIZED
+ * @see #SUBTEXT_TRACK_UNSUPPORTED
* @see #SUBTEXT_CUSTOM
*/
@SubText
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 85b02ad..720d9a6 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -63,6 +63,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1009,6 +1010,13 @@
* @param buffer the {@link AdBuffer} that was consumed.
*/
public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) {
+ AdBuffer dupBuffer;
+ try {
+ dupBuffer = AdBuffer.dupAdBuffer(buffer);
+ } catch (IOException e) {
+ Log.w(TAG, "dup AdBuffer error in notifyAdBufferConsumed:", e);
+ return;
+ }
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
@@ -1016,10 +1024,14 @@
try {
if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed");
if (mSessionCallback != null) {
- mSessionCallback.onAdBufferConsumed(buffer);
+ mSessionCallback.onAdBufferConsumed(dupBuffer);
}
} catch (RemoteException e) {
Log.w(TAG, "error in notifyAdBufferConsumed", e);
+ } finally {
+ if (dupBuffer != null) {
+ dupBuffer.getSharedMemory().close();
+ }
}
}
});
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index ec85cc7..2419404 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -1964,6 +1964,13 @@
*/
@CallSuper
public void notifyAdBufferReady(@NonNull AdBuffer buffer) {
+ AdBuffer dupBuffer;
+ try {
+ dupBuffer = AdBuffer.dupAdBuffer(buffer);
+ } catch (IOException e) {
+ Log.w(TAG, "dup AdBuffer error in notifyAdBufferReady:", e);
+ return;
+ }
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
@@ -1974,10 +1981,14 @@
"notifyAdBufferReady(buffer=" + buffer + ")");
}
if (mSessionCallback != null) {
- mSessionCallback.onAdBufferReady(AdBuffer.dupAdBuffer(buffer));
+ mSessionCallback.onAdBufferReady(dupBuffer);
}
- } catch (RemoteException | IOException e) {
+ } catch (RemoteException e) {
Log.w(TAG, "error in notifyAdBuffer", e);
+ } finally {
+ if (dupBuffer != null) {
+ dupBuffer.getSharedMemory().close();
+ }
}
}
});
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index d2c1c042..917276b 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -75,5 +75,11 @@
Notify purchase failed
</button>
<p id="purchase_failed"></p>
+
+ <h2>Dismiss flow</h2>
+ <button type="button" onclick="testDismissFlow()">
+ Dismiss flow
+ </button>
+ <p id="dismiss_flow"></p>
</body>
</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 84ab1f9..be397a1 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -31,3 +31,9 @@
document.getElementById("purchase_failed").innerHTML =
"Notified purchase failed.";
}
+
+function testDismissFlow() {
+ DataBoostWebServiceFlow.dismissFlow();
+ document.getElementById("dismiss_flow").innerHTML =
+ "Called dismiss flow.";
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 0aadd31..4500a22 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -88,4 +88,21 @@
@Nullable String failureReason) {
mActivity.onPurchaseFailed(failureCode, failureReason);
}
+
+ /**
+ * Interface method allowing the carrier website to notify the slice purchase application that
+ * the service flow ended prematurely. This can be due to user action, an error in the
+ * web sheet logic, or an error on the network side.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
+ * function dismissFlow() {
+ * DataBoostWebServiceFlow.dismissFlow();
+ * }
+ * </script>
+ */
+ @JavascriptInterface
+ public void dismissFlow() {
+ mActivity.onDismissFlow();
+ }
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index d304394..2530257d6 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -27,6 +27,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
+import android.webkit.CookieManager;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
@@ -137,6 +138,14 @@
finishAndRemoveTask();
}
+ protected void onDismissFlow() {
+ logd("onDismissFlow: Dismiss flow called while purchasing premium capability "
+ + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ finishAndRemoveTask();
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
// Pressing back in the WebView will go to the previous page instead of closing
@@ -168,6 +177,12 @@
// Create WebView
mWebView = new WebView(this);
+ // Clear any cookies and state that might be saved from previous sessions
+ CookieManager.getInstance().removeAllCookies(null);
+ CookieManager.getInstance().flush();
+ mWebView.clearCache(true);
+ mWebView.clearHistory();
+
// Enable JavaScript for the carrier purchase website to send results back to
// the slice purchase application.
mWebView.getSettings().setJavaScriptEnabled(true);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index e7a75e5..cc103fa 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -55,6 +55,7 @@
@Mock PendingIntent mPendingIntent;
@Mock PendingIntent mSuccessfulIntent;
@Mock PendingIntent mCanceledIntent;
+ @Mock PendingIntent mRequestFailedIntent;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock NotificationManager mNotificationManager;
@Mock PersistableBundle mPersistableBundle;
@@ -112,6 +113,11 @@
doReturn(true).when(mSuccessfulIntent).isBroadcast();
doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mRequestFailedIntent)
+ .getCreatorPackage();
+ doReturn(true).when(mRequestFailedIntent).isBroadcast();
+ doReturn(mRequestFailedIntent).when(spiedIntent).getParcelableExtra(
+ eq(SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED), eq(PendingIntent.class));
mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
}
@@ -124,7 +130,7 @@
@Test
public void testOnPurchaseFailed() throws Exception {
- int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+ int failureCode = SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE;
String failureReason = "Server unreachable";
mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -141,4 +147,10 @@
mSlicePurchaseActivity.onDestroy();
verify(mCanceledIntent).send();
}
+
+ @Test
+ public void testOnDismissFlow() throws Exception {
+ mSlicePurchaseActivity.onDismissFlow();
+ verify(mRequestFailedIntent).send();
+ }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
index 5eaa495..460bf99 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -21,6 +21,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
@@ -34,42 +35,48 @@
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp,
- letterSpacing = (-0.2).sp
+ letterSpacing = (-0.2).sp,
+ hyphens = Hyphens.Auto,
),
displayMedium = TextStyle(
fontFamily = brand,
fontWeight = FontWeight.Normal,
fontSize = 45.sp,
lineHeight = 52.sp,
- letterSpacing = 0.0.sp
+ letterSpacing = 0.0.sp,
+ hyphens = Hyphens.Auto,
),
displaySmall = TextStyle(
fontFamily = brand,
fontWeight = FontWeight.Normal,
fontSize = 36.sp,
lineHeight = 44.sp,
- letterSpacing = 0.0.sp
+ letterSpacing = 0.0.sp,
+ hyphens = Hyphens.Auto,
),
headlineLarge = TextStyle(
fontFamily = brand,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 40.sp,
- letterSpacing = 0.0.sp
+ letterSpacing = 0.0.sp,
+ hyphens = Hyphens.Auto,
),
headlineMedium = TextStyle(
fontFamily = brand,
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp,
- letterSpacing = 0.0.sp
+ letterSpacing = 0.0.sp,
+ hyphens = Hyphens.Auto,
),
headlineSmall = TextStyle(
fontFamily = brand,
fontWeight = FontWeight.Normal,
fontSize = 24.sp,
lineHeight = 32.sp,
- letterSpacing = 0.0.sp
+ letterSpacing = 0.0.sp,
+ hyphens = Hyphens.Auto,
),
titleLarge = TextStyle(
fontFamily = brand,
@@ -77,6 +84,7 @@
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.02.em,
+ hyphens = Hyphens.Auto,
),
titleMedium = TextStyle(
fontFamily = brand,
@@ -84,6 +92,7 @@
fontSize = 20.sp,
lineHeight = 24.sp,
letterSpacing = 0.02.em,
+ hyphens = Hyphens.Auto,
),
titleSmall = TextStyle(
fontFamily = brand,
@@ -91,6 +100,7 @@
fontSize = 18.sp,
lineHeight = 20.sp,
letterSpacing = 0.02.em,
+ hyphens = Hyphens.Auto,
),
bodyLarge = TextStyle(
fontFamily = plain,
@@ -98,6 +108,7 @@
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
bodyMedium = TextStyle(
fontFamily = plain,
@@ -105,6 +116,7 @@
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
bodySmall = TextStyle(
fontFamily = plain,
@@ -112,6 +124,7 @@
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
labelLarge = TextStyle(
fontFamily = plain,
@@ -119,6 +132,7 @@
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
labelMedium = TextStyle(
fontFamily = plain,
@@ -126,6 +140,7 @@
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
labelSmall = TextStyle(
fontFamily = plain,
@@ -133,6 +148,7 @@
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.01.em,
+ hyphens = Hyphens.Auto,
),
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index 5f2344e..01ba8f8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -29,6 +30,7 @@
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.toMediumWeight
@@ -78,7 +80,7 @@
@Composable
fun PlaceholderTitle(title: String) {
Box(
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPadding),
contentAlignment = Alignment.Center,
) {
Text(
diff --git a/packages/SettingsLib/TEST_MAPPING b/packages/SettingsLib/TEST_MAPPING
new file mode 100644
index 0000000..f6ada4c1a
--- /dev/null
+++ b/packages/SettingsLib/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "SettingsLibUnitTests"
+ },
+ {
+ "name": "SpaPrivilegedLibTests"
+ },
+ {
+ "name": "SettingsSpaUnitTests"
+ }
+ ]
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f581091..dac7f8d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1327,6 +1327,12 @@
<string name="media_transfer_this_device_name" product="default">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name" product="tablet">This tablet</string>
+ <!-- Name of the dock device. [CHAR LIMIT=30] -->
+ <string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
+ <!-- Default name of the external device. [CHAR LIMIT=30] -->
+ <string name="media_transfer_external_device_name">External Device</string>
+ <!-- Default name of the connected device. [CHAR LIMIT=30] -->
+ <string name="media_transfer_default_device_name">Connected device</string>
<!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
<string name="media_transfer_this_phone">This phone</string>
<!-- Sub status indicates device is not available due to an unknown error. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 2584be7..4ac4aae 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -17,6 +17,7 @@
<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearanceSmall">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearanceMedium">
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
@@ -91,7 +92,7 @@
<item name="android:ellipsize">end</item>
</style>
- <style name="DialogButtonPositive" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <style name="DialogButtonPositive">
<item name="android:buttonCornerRadius">0dp</item>
<item name="android:background">@drawable/dialog_btn_filled</item>
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
@@ -105,7 +106,6 @@
<style name="DialogButtonNegative">
<item name="android:buttonCornerRadius">28dp</item>
<item name="android:background">@drawable/dialog_btn_outline</item>
- <item name="android:buttonCornerRadius">28dp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20sp</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index a80061e..2bca7cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -43,39 +43,5 @@
/**
* Bluetooth scheme.
*/
- public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
-
- // BluetoothLeBroadcastMetadata
- static final String PREFIX_BT_ADDRESS_TYPE = "T:";
- static final String PREFIX_BT_DEVICE = "D:";
- static final String PREFIX_BT_ADVERTISING_SID = "AS:";
- static final String PREFIX_BT_BROADCAST_ID = "B:";
- static final String PREFIX_BT_SYNC_INTERVAL = "SI:";
- static final String PREFIX_BT_IS_ENCRYPTED = "E:";
- static final String PREFIX_BT_BROADCAST_CODE = "C:";
- static final String PREFIX_BT_PRESENTATION_DELAY = "PD:";
- static final String PREFIX_BT_SUBGROUPS = "SG:";
- static final String PREFIX_BT_ANDROID_VERSION = "V:";
-
- // BluetoothLeBroadcastSubgroup
- static final String PREFIX_BTSG_CODEC_ID = "CID:";
- static final String PREFIX_BTSG_CODEC_CONFIG = "CC:";
- static final String PREFIX_BTSG_AUDIO_CONTENT = "AC:";
- static final String PREFIX_BTSG_CHANNEL_PREF = "CP:";
- static final String PREFIX_BTSG_BROADCAST_CHANNEL = "BC:";
-
- // BluetoothLeAudioCodecConfigMetadata
- static final String PREFIX_BTCC_AUDIO_LOCATION = "AL:";
- static final String PREFIX_BTCC_RAW_METADATA = "CCRM:";
-
- // BluetoothLeAudioContentMetadata
- static final String PREFIX_BTAC_PROGRAM_INFO = "PI:";
- static final String PREFIX_BTAC_LANGUAGE = "L:";
- static final String PREFIX_BTAC_RAW_METADATA = "ACRM:";
-
- // BluetoothLeBroadcastChannel
- static final String PREFIX_BTBC_CHANNEL_INDEX = "CI:";
- static final String PREFIX_BTBC_CODEC_CONFIG = "BCCM:";
-
- static final String DELIMITER_QR_CODE = ";";
+ public static final String SCHEME_BT_BROADCAST_METADATA = "BT:BluetoothLeBroadcastMetadata:";
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
new file mode 100644
index 0000000..b54b115
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.os.Parcel
+import android.os.Parcelable
+import android.util.Base64
+import android.util.Log
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA
+
+object BluetoothLeBroadcastMetadataExt {
+ private const val TAG = "BluetoothLeBroadcastMetadataExt"
+
+ /**
+ * Converts [BluetoothLeBroadcastMetadata] to QR code string.
+ *
+ * QR code string will prefix with "BT:BluetoothLeBroadcastMetadata:".
+ */
+ fun BluetoothLeBroadcastMetadata.toQrCodeString(): String =
+ SCHEME_BT_BROADCAST_METADATA + Base64.encodeToString(toBytes(this), Base64.NO_WRAP)
+
+ /**
+ * Converts QR code string to [BluetoothLeBroadcastMetadata].
+ *
+ * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
+ */
+ fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
+ if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) return null
+ return try {
+ val encodedString = qrCodeString.removePrefix(SCHEME_BT_BROADCAST_METADATA)
+ val bytes = Base64.decode(encodedString, Base64.NO_WRAP)
+ createFromBytes(BluetoothLeBroadcastMetadata.CREATOR, bytes)
+ } catch (e: Exception) {
+ Log.w(TAG, "Cannot convert QR code string to BluetoothLeBroadcastMetadata", e)
+ null
+ }
+ }
+
+ private fun toBytes(parcelable: Parcelable): ByteArray =
+ Parcel.obtain().run {
+ parcelable.writeToParcel(this, 0)
+ setDataPosition(0)
+ val bytes = marshall()
+ recycle()
+ bytes
+ }
+
+ private fun <T> createFromBytes(creator: Parcelable.Creator<T>, bytes: ByteArray): T =
+ Parcel.obtain().run {
+ unmarshall(bytes, 0, bytes.size)
+ setDataPosition(0)
+ val created = creator.createFromParcel(this)
+ recycle()
+ created
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
deleted file mode 100644
index 0630a2e..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright (C) 2022 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;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
-import android.bluetooth.BluetoothLeAudioContentMetadata;
-import android.bluetooth.BluetoothLeBroadcastChannel;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastSubgroup;
-import android.util.Log;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class LocalBluetoothLeBroadcastMetadata {
- private static final boolean DEBUG = BluetoothUtils.D;
- private static final String TAG = "LocalBluetoothLeBroadcastMetadata";
- private static final String METADATA_START = "<";
- private static final String METADATA_END = ">";
- private static final String PATTERN_REGEX = "<(.*?)>";
- private static final String PATTERN_BT_BROADCAST_METADATA =
- "T:<(.*?)>;+D:<(.*?)>;+AS:<(.*?)>;+B:<(.*?)>;+SI:<(.*?)>;+E:<(.*?)>;+C:<(.*?)>;"
- + "+PD:<(.*?)>;+SG:(.*)";
- private static final String PATTERN_BT_SUBGROUP =
- "CID:<(.*?)>;+CC:<(.*?);>;+AC:<(.*?);>;+CP:<(.*?)>;+BC:<(.*)>;>;";
- private static final String PATTERN_BT_CHANNEL = "CI:<(.*?)>;+BCCM:<(.*?);>;";
-
- /* Index for BluetoothLeBroadcastMetadata */
- private static int MATCH_INDEX_ADDRESS_TYPE = 1;
- private static int MATCH_INDEX_DEVICE = 2;
- private static int MATCH_INDEX_ADVERTISING_SID = 3;
- private static int MATCH_INDEX_BROADCAST_ID = 4;
- private static int MATCH_INDEX_SYNC_INTERVAL = 5;
- private static int MATCH_INDEX_IS_ENCRYPTED = 6;
- private static int MATCH_INDEX_BROADCAST_CODE = 7;
- private static int MATCH_INDEX_PRESENTATION_DELAY = 8;
- private static int MATCH_INDEX_SUBGROUPS = 9;
-
- /* Index for BluetoothLeBroadcastSubgroup */
- private static int MATCH_INDEX_CODEC_ID = 1;
- private static int MATCH_INDEX_CODEC_CONFIG = 2;
- private static int MATCH_INDEX_AUDIO_CONTENT = 3;
- private static int MATCH_INDEX_CHANNEL_PREF = 4;
- private static int MATCH_INDEX_BROADCAST_CHANNEL = 5;
-
- /* Index for BluetoothLeAudioCodecConfigMetadata */
- private static int LIST_INDEX_AUDIO_LOCATION = 0;
- private static int LIST_INDEX_CODEC_CONFIG_RAW_METADATA = 1;
-
- /* Index for BluetoothLeAudioContentMetadata */
- private static int LIST_INDEX_PROGRAM_INFO = 0;
- private static int LIST_INDEX_LANGUAGE = 1;
- private static int LIST_INDEX_AUDIO_CONTENT_RAW_METADATA = 2;
-
- /* Index for BluetoothLeBroadcastChannel */
- private static int MATCH_INDEX_CHANNEL_INDEX = 1;
- private static int MATCH_INDEX_CHANNEL_CODEC_CONFIG = 2;
-
- private BluetoothLeBroadcastSubgroup mSubgroup;
- private List<BluetoothLeBroadcastSubgroup> mSubgroupList;
-
- // BluetoothLeBroadcastMetadata
- // Optional: Identity address type
- private int mSourceAddressType;
- // Optional: Must use identity address
- private BluetoothDevice mSourceDevice;
- private int mSourceAdvertisingSid;
- private int mBroadcastId;
- private int mPaSyncInterval;
- private int mPresentationDelayMicros;
- private boolean mIsEncrypted;
- private byte[] mBroadcastCode;
-
- // BluetoothLeBroadcastSubgroup
- private int mCodecId;
- private BluetoothLeAudioContentMetadata mContentMetadata;
- private BluetoothLeAudioCodecConfigMetadata mConfigMetadata;
- private Boolean mNoChannelPreference;
- private List<BluetoothLeBroadcastChannel> mChannel;
-
- // BluetoothLeAudioCodecConfigMetadata
- private long mAudioLocation;
- private byte[] mCodecConfigMetadata;
-
- // BluetoothLeAudioContentMetadata
- private String mLanguage;
- private String mProgramInfo;
- private byte[] mAudioContentMetadata;
-
- // BluetoothLeBroadcastChannel
- private boolean mIsSelected;
- private int mChannelIndex;
-
-
- LocalBluetoothLeBroadcastMetadata(BluetoothLeBroadcastMetadata metadata) {
- mSourceAddressType = metadata.getSourceAddressType();
- mSourceDevice = metadata.getSourceDevice();
- mSourceAdvertisingSid = metadata.getSourceAdvertisingSid();
- mBroadcastId = metadata.getBroadcastId();
- mPaSyncInterval = metadata.getPaSyncInterval();
- mIsEncrypted = metadata.isEncrypted();
- mBroadcastCode = metadata.getBroadcastCode();
- mPresentationDelayMicros = metadata.getPresentationDelayMicros();
- mSubgroupList = metadata.getSubgroups();
- }
-
- public LocalBluetoothLeBroadcastMetadata() {
- }
-
- public void setBroadcastCode(byte[] code) {
- mBroadcastCode = code;
- }
-
- public int getBroadcastId() {
- return mBroadcastId;
- }
-
- public String convertToQrCodeString() {
- String subgroupString = convertSubgroupToString(mSubgroupList);
- return new StringBuilder()
- .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
- .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
- .append(METADATA_START).append(mSourceAddressType).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_DEVICE)
- .append(METADATA_START).append(mSourceDevice).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_ADVERTISING_SID)
- .append(METADATA_START).append(mSourceAdvertisingSid).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_ID)
- .append(METADATA_START).append(mBroadcastId).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_SYNC_INTERVAL)
- .append(METADATA_START).append(mPaSyncInterval).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_IS_ENCRYPTED)
- .append(METADATA_START).append(mIsEncrypted).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_BROADCAST_CODE)
- .append(METADATA_START).append(Arrays.toString(mBroadcastCode)).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_PRESENTATION_DELAY)
- .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
- .append(METADATA_START).append(subgroupString).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .toString();
- }
-
- private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
- StringBuilder subgroupListBuilder = new StringBuilder();
- String subgroupString = "";
- for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
- String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
- String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
- boolean hasChannelPreference = subgroup.hasChannelPreference();
- String channels = convertChannelToString(subgroup.getChannels());
- subgroupString = new StringBuilder()
- .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
- .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
- .append(METADATA_START).append(audioCodec).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
- .append(METADATA_START).append(audioContent).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTSG_CHANNEL_PREF)
- .append(METADATA_START).append(hasChannelPreference).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
- .append(METADATA_START).append(channels).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .toString();
- subgroupListBuilder.append(subgroupString);
- }
- return subgroupListBuilder.toString();
- }
-
- private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
- String audioLocation = String.valueOf(config.getAudioLocation());
- String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
- return new StringBuilder()
- .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
- .append(METADATA_START).append(audioLocation).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
- .append(METADATA_START).append(rawMetadata).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .toString();
- }
-
- private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
- String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
- return new StringBuilder()
- .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
- .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
- .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
- .append(METADATA_START).append(rawMetadata).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .toString();
- }
-
- private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
- StringBuilder channelListBuilder = new StringBuilder();
- String channelString = "";
- for (BluetoothLeBroadcastChannel channel: channelList) {
- String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
- channelString = new StringBuilder()
- .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
- .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
- .append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
- .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
- .toString();
- channelListBuilder.append(channelString);
- }
- return channelListBuilder.toString();
- }
-
- /**
- * Example : prefix is with the “BT:”, and end by the Android Version.
- * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
- *
- * @return BluetoothLeBroadcastMetadata
- */
- public BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
- if (DEBUG) {
- Log.d(TAG, "Convert " + qrCodeString + "to BluetoothLeBroadcastMetadata");
- }
-
- Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA);
- Matcher match = pattern.matcher(qrCodeString);
- if (match.find()) {
- try {
- mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE));
- mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
- match.group(MATCH_INDEX_DEVICE));
- mSourceAdvertisingSid = Integer.parseInt(
- match.group(MATCH_INDEX_ADVERTISING_SID));
- mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID));
- mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL));
- mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED));
- mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes();
- mPresentationDelayMicros =
- Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY));
-
- if (DEBUG) {
- Log.d(TAG, "Converted qrCodeString result: "
- + " ,Type = " + mSourceAddressType
- + " ,Device = " + mSourceDevice
- + " ,AdSid = " + mSourceAdvertisingSid
- + " ,BroadcastId = " + mBroadcastId
- + " ,paSync = " + mPaSyncInterval
- + " ,encrypted = " + mIsEncrypted
- + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode)
- + " ,delay = " + mPresentationDelayMicros);
- }
-
- mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS));
-
- return new BluetoothLeBroadcastMetadata.Builder()
- .setSourceDevice(mSourceDevice, mSourceAddressType)
- .setSourceAdvertisingSid(mSourceAdvertisingSid)
- .setBroadcastId(mBroadcastId)
- .setPaSyncInterval(mPaSyncInterval)
- .setEncrypted(mIsEncrypted)
- .setBroadcastCode(mBroadcastCode)
- .setPresentationDelayMicros(mPresentationDelayMicros)
- .addSubgroup(mSubgroup)
- .build();
- } catch (IllegalArgumentException e) {
- Log.d(TAG, "IllegalArgumentException when convert : " + e);
- return null;
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata.");
- }
- return null;
- }
- }
-
- private BluetoothLeBroadcastSubgroup convertToSubgroup(String subgroupString) {
- if (DEBUG) {
- Log.d(TAG, "Convert " + subgroupString + "to BluetoothLeBroadcastSubgroup");
- }
- Pattern pattern = Pattern.compile(PATTERN_BT_SUBGROUP);
- Matcher match = pattern.matcher(subgroupString);
- if (match.find()) {
- mCodecId = Integer.parseInt(match.group(MATCH_INDEX_CODEC_ID));
- mConfigMetadata = convertToConfigMetadata(match.group(MATCH_INDEX_CODEC_CONFIG));
- mContentMetadata = convertToContentMetadata(match.group(MATCH_INDEX_AUDIO_CONTENT));
- mNoChannelPreference = Boolean.valueOf(match.group(MATCH_INDEX_CHANNEL_PREF));
- mChannel =
- convertToChannel(match.group(MATCH_INDEX_BROADCAST_CHANNEL), mConfigMetadata);
-
- BluetoothLeBroadcastSubgroup.Builder subgroupBuilder =
- new BluetoothLeBroadcastSubgroup.Builder();
- subgroupBuilder.setCodecId(mCodecId);
- subgroupBuilder.setCodecSpecificConfig(mConfigMetadata);
- subgroupBuilder.setContentMetadata(mContentMetadata);
-
- for (BluetoothLeBroadcastChannel channel : mChannel) {
- subgroupBuilder.addChannel(channel);
- }
- return subgroupBuilder.build();
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "The match fail, can not convert it to BluetoothLeBroadcastSubgroup.");
- }
- return null;
- }
- }
-
- private BluetoothLeAudioCodecConfigMetadata convertToConfigMetadata(
- String configMetadataString) {
- if (DEBUG) {
- Log.d(TAG,
- "Convert " + configMetadataString + "to BluetoothLeAudioCodecConfigMetadata");
- }
- Pattern pattern = Pattern.compile(PATTERN_REGEX);
- Matcher match = pattern.matcher(configMetadataString);
- ArrayList<String> resultList = new ArrayList<>();
- while (match.find()) {
- resultList.add(match.group(1));
- Log.d(TAG, "Codec Config match : " + match.group(1));
- }
- if (DEBUG) {
- Log.d(TAG, "Converted configMetadataString result: " + resultList.size());
- }
- if (resultList.size() > 0) {
- mAudioLocation = Long.parseLong(resultList.get(LIST_INDEX_AUDIO_LOCATION));
- mCodecConfigMetadata = resultList.get(LIST_INDEX_CODEC_CONFIG_RAW_METADATA).getBytes();
- return new BluetoothLeAudioCodecConfigMetadata.Builder()
- .setAudioLocation(mAudioLocation)
- .build();
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "The match fail, can not convert it to "
- + "BluetoothLeAudioCodecConfigMetadata.");
- }
- return null;
- }
- }
-
- private BluetoothLeAudioContentMetadata convertToContentMetadata(String contentMetadataString) {
- if (DEBUG) {
- Log.d(TAG, "Convert " + contentMetadataString + "to BluetoothLeAudioContentMetadata");
- }
- Pattern pattern = Pattern.compile(PATTERN_REGEX);
- Matcher match = pattern.matcher(contentMetadataString);
- ArrayList<String> resultList = new ArrayList<>();
- while (match.find()) {
- Log.d(TAG, "Audio Content match : " + match.group(1));
- resultList.add(match.group(1));
- }
- if (DEBUG) {
- Log.d(TAG, "Converted contentMetadataString result: " + resultList.size());
- }
- if (resultList.size() > 0) {
- mProgramInfo = resultList.get(LIST_INDEX_PROGRAM_INFO);
- mLanguage = resultList.get(LIST_INDEX_LANGUAGE);
- mAudioContentMetadata =
- resultList.get(LIST_INDEX_AUDIO_CONTENT_RAW_METADATA).getBytes();
-
- /* TODO(b/265253566) : Need to set the default value for language when the user starts
- * the broadcast.
- */
- if (mLanguage.equals("null")) {
- mLanguage = "eng";
- }
-
- return new BluetoothLeAudioContentMetadata.Builder()
- .setProgramInfo(mProgramInfo)
- .setLanguage(mLanguage)
- .build();
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "The match fail, can not convert it to BluetoothLeAudioContentMetadata.");
- }
- return null;
- }
- }
-
- private List<BluetoothLeBroadcastChannel> convertToChannel(String channelString,
- BluetoothLeAudioCodecConfigMetadata configMetadata) {
- if (DEBUG) {
- Log.d(TAG, "Convert " + channelString + "to BluetoothLeBroadcastChannel");
- }
- Pattern pattern = Pattern.compile(PATTERN_BT_CHANNEL);
- Matcher match = pattern.matcher(channelString);
- Map<Integer, BluetoothLeAudioCodecConfigMetadata> channel =
- new HashMap<Integer, BluetoothLeAudioCodecConfigMetadata>();
- while (match.find()) {
- channel.put(Integer.parseInt(match.group(MATCH_INDEX_CHANNEL_INDEX)),
- convertToConfigMetadata(match.group(MATCH_INDEX_CHANNEL_CODEC_CONFIG)));
- }
-
- if (channel.size() > 0) {
- mIsSelected = false;
- ArrayList<BluetoothLeBroadcastChannel> broadcastChannelList = new ArrayList<>();
- for (Map.Entry<Integer, BluetoothLeAudioCodecConfigMetadata> entry :
- channel.entrySet()) {
-
- broadcastChannelList.add(
- new BluetoothLeBroadcastChannel.Builder()
- .setSelected(mIsSelected)
- .setChannelIndex(entry.getKey())
- .setCodecMetadata(entry.getValue())
- .build());
- }
- return broadcastChannelList;
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "The match fail, can not convert it to BluetoothLeBroadcastChannel.");
- }
- return null;
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt
new file mode 100644
index 0000000..870ea8d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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
+
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
+
+@Deprecated("Replace with BluetoothLeBroadcastMetadataExt")
+class LocalBluetoothLeBroadcastMetadata(private val metadata: BluetoothLeBroadcastMetadata?) {
+
+ constructor() : this(null)
+
+ fun convertToQrCodeString(): String = metadata?.toQrCodeString() ?: ""
+
+ fun convertToBroadcastMetadata(qrCodeString: String) =
+ BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(qrCodeString)
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 76556639..1251b0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -196,6 +196,9 @@
/** Gets the battery level from the intent. */
public static int getBatteryLevel(Intent batteryChangedIntent) {
+ if (batteryChangedIntent == null) {
+ return -1; /*invalid battery level*/
+ }
final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
return scale == 0
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 1c82be9..34519c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -70,13 +70,17 @@
name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
break;
case TYPE_DOCK:
- case TYPE_HDMI:
- name = mRouteInfo.getName();
+ name = mContext.getString(R.string.media_transfer_dock_speaker_device_name);
break;
case TYPE_BUILTIN_SPEAKER:
- default:
name = mContext.getString(R.string.media_transfer_this_device_name);
break;
+ case TYPE_HDMI:
+ name = mContext.getString(R.string.media_transfer_external_device_name);
+ break;
+ default:
+ name = mContext.getString(R.string.media_transfer_default_device_name);
+ break;
}
return name.toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
index e835125..e6b1cfb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
@@ -16,14 +16,13 @@
package com.android.settingslib.mobile.dataservice;
-import java.util.List;
-
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
-import androidx.room.Update;
+
+import java.util.List;
@Dao
public interface SubscriptionInfoDao {
@@ -32,7 +31,9 @@
void insertSubsInfo(SubscriptionInfoEntity... subscriptionInfo);
@Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " ORDER BY "
- + DataServiceUtils.SubscriptionInfoData.COLUMN_ID)
+ + " CASE WHEN " + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX
+ + " >= 0 THEN 1 ELSE 2 END , "
+ + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
LiveData<List<SubscriptionInfoEntity>> queryAvailableSubInfos();
@Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
@@ -43,7 +44,8 @@
+ DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID
+ " = :isActiveSubscription" + " AND "
+ DataServiceUtils.SubscriptionInfoData.COLUMN_IS_SUBSCRIPTION_VISIBLE
- + " = :isSubscriptionVisible")
+ + " = :isSubscriptionVisible" + " ORDER BY "
+ + DataServiceUtils.SubscriptionInfoData.COLUMN_SIM_SLOT_INDEX)
LiveData<List<SubscriptionInfoEntity>> queryActiveSubInfos(
boolean isActiveSubscription, boolean isSubscriptionVisible);
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
new file mode 100644
index 0000000..a4558f1
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "SettingsLibUnitTests",
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SettingsLib",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "truth-prebuilt",
+ ],
+}
diff --git a/packages/SettingsLib/tests/unit/AndroidManifest.xml b/packages/SettingsLib/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..568f9cb
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.test">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for SettingsLib"
+ android:targetPackage="com.android.settingslib.test">
+ </instrumentation>
+</manifest>
diff --git a/packages/SettingsLib/tests/unit/OWNERS b/packages/SettingsLib/tests/unit/OWNERS
new file mode 100644
index 0000000..66559252
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/OWNERS
@@ -0,0 +1,2 @@
+# We do not guard tests - everyone is welcomed to contribute to tests.
+per-file *.kt=*
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
new file mode 100644
index 0000000..0e3590d
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata
+import android.bluetooth.BluetoothLeAudioContentMetadata
+import android.bluetooth.BluetoothLeBroadcastChannel
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothLeBroadcastSubgroup
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt.toQrCodeString
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BluetoothLeBroadcastMetadataExtTest {
+
+ @Test
+ fun toQrCodeString() {
+ val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+ setCodecId(100)
+ val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
+ setCodecSpecificConfig(audioCodecConfigMetadata)
+ setContentMetadata(BluetoothLeAudioContentMetadata.Builder().build())
+ addChannel(BluetoothLeBroadcastChannel.Builder().apply {
+ setChannelIndex(1000)
+ setCodecMetadata(audioCodecConfigMetadata)
+ }.build())
+ }.build()
+
+ val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
+ setSourceDevice(Device, 0)
+ setSourceAdvertisingSid(1)
+ setBroadcastId(2)
+ setPaSyncInterval(3)
+ setEncrypted(true)
+ setBroadcastCode(byteArrayOf(10, 11, 12, 13))
+ setPresentationDelayMicros(4)
+ addSubgroup(subgroup)
+ }.build()
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
+ }
+
+ @Test
+ fun decodeAndEncodeAgain_sameString() {
+ val metadata = BluetoothLeBroadcastMetadataExt.convertToBroadcastMetadata(QR_CODE_STRING)!!
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
+ }
+
+ private companion object {
+ const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+
+ val Device: BluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_DEVICE_ADDRESS)
+
+ const val QR_CODE_STRING =
+ "BT:BluetoothLeBroadcastMetadata:AAAAAAEAAAABAAAAEQAAADAAMAA6AEEAMQA6AEEAMQA6AEEAMQA6" +
+ "AEEAMQA6AEEAMQAAAAAAAAABAAAAAgAAAAMAAAABAAAABAAAAAQAAAAKCwwNBAAAAAEAAAABAAAAZAAA" +
+ "AAAAAAABAAAAAAAAAAAAAAAGAAAABgAAAAUDAAAAAAAAAAAAAAAAAAAAAAAAAQAAAP//////////AAAA" +
+ "AAAAAAABAAAAAQAAAAAAAADoAwAAAQAAAAAAAAAAAAAABgAAAAYAAAAFAwAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAAD/////AAAAAAAAAAA="
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b661ba4..476c820 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,6 +176,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
+ <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
new file mode 100644
index 0000000..5d09de1
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.compose.animation
+
+import androidx.compose.animation.core.Easing
+import androidx.core.animation.Interpolator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * Compose-compatible definition of Android motion eases, see
+ * https://carbon.googleplex.com/android-motion/pages/easing
+ */
+object Easings {
+
+ /** The standard interpolator that should be used on every normal animation */
+ val Standard = fromInterpolator(InterpolatorsAndroidX.STANDARD)
+
+ /**
+ * The standard accelerating interpolator that should be used on every regular movement of
+ * content that is disappearing e.g. when moving off screen.
+ */
+ val StandardAccelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ val StandardDecelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
+
+ /** The default emphasized interpolator. Used for hero / emphasized movement of content. */
+ val Emphasized = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
+
+ /**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ val EmphasizedAccelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ val EmphasizedDecelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
+
+ /** The linear interpolator. */
+ val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+
+ private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index 954bad56..d364374 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.ui.composable
+import android.content.Context
import com.android.systemui.bouncer.ui.composable.BouncerScene
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +29,7 @@
import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.shade.ui.composable.ShadeScene
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -57,6 +59,7 @@
@SysUISingleton
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
fun bouncerScene(
+ @Application context: Context,
viewModelFactory: BouncerViewModel.Factory,
): BouncerScene {
return BouncerScene(
@@ -64,6 +67,7 @@
viewModelFactory.create(
containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
),
+ dialogFactory = { SystemUIDialog(context) },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 3c74ef5..240bace 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -14,9 +14,16 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package com.android.systemui.bouncer.ui.composable
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.DialogInterface
import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -26,15 +33,20 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
@@ -51,6 +63,7 @@
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
class BouncerScene(
private val viewModel: BouncerViewModel,
+ private val dialogFactory: () -> AlertDialog,
) : ComposableScene {
override val key = SceneKey.Bouncer
@@ -68,16 +81,19 @@
override fun Content(
containerName: String,
modifier: Modifier,
- ) = BouncerScene(viewModel, modifier)
+ ) = BouncerScene(viewModel, dialogFactory, modifier)
}
@Composable
private fun BouncerScene(
viewModel: BouncerViewModel,
+ dialogFactory: () -> AlertDialog,
modifier: Modifier = Modifier,
) {
- val message: String by viewModel.message.collectAsState()
+ val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
+ val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
+ var dialog: Dialog? by remember { mutableStateOf(null) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -88,9 +104,10 @@
Crossfade(
targetState = message,
label = "Bouncer message",
- ) {
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ ) { message ->
Text(
- text = it,
+ text = message.text,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyLarge,
)
@@ -132,5 +149,26 @@
style = MaterialTheme.typography.bodyMedium,
)
}
+
+ if (dialogMessage != null) {
+ if (dialog == null) {
+ dialog =
+ dialogFactory().apply {
+ setMessage(dialogMessage)
+ setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.ok),
+ ) { _, _ ->
+ viewModel.onThrottlingDialogDismissed()
+ }
+ setCancelable(false)
+ setCanceledOnTouchOutside(false)
+ show()
+ }
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 4e85621..7545ff4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -53,6 +53,8 @@
) {
val focusRequester = remember { FocusRequester() }
val password: String by viewModel.password.collectAsState()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
LaunchedEffect(Unit) {
// When the UI comes up, request focus on the TextField to bring up the software keyboard.
@@ -61,6 +63,13 @@
viewModel.onShown()
}
+ LaunchedEffect(animateFailure) {
+ if (animateFailure) {
+ // We don't currently have a failure animation for password, just consume it:
+ viewModel.onFailureAnimationShown()
+ }
+ }
+
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier,
@@ -71,6 +80,7 @@
TextField(
value = password,
onValueChange = viewModel::onPasswordInputChanged,
+ enabled = isInputEnabled,
visualTransformation = PasswordVisualTransformation(),
singleLine = true,
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 3afd33f..b3d2e35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -18,6 +18,7 @@
import android.view.HapticFeedbackConstants
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
@@ -41,12 +42,15 @@
import androidx.compose.ui.res.integerResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
import com.android.internal.R
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import com.android.systemui.compose.modifiers.thenIf
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/**
@@ -66,9 +70,9 @@
val rowCount = viewModel.rowCount
val dotColor = MaterialTheme.colorScheme.secondary
- val dotRadius = with(LocalDensity.current) { 8.dp.toPx() }
+ val dotRadius = with(LocalDensity.current) { (DOT_DIAMETER_DP / 2).dp.toPx() }
val lineColor = MaterialTheme.colorScheme.primary
- val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() }
+ val lineStrokeWidth = with(LocalDensity.current) { LINE_STROKE_WIDTH_DP.dp.toPx() }
var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
val horizontalSpacing = containerSize.width / colCount
@@ -82,6 +86,9 @@
val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState()
// The dots selected so far, if the user is currently dragging.
val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+ val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
// Map of animatables for the scale of each dot, keyed by dot.
val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
@@ -96,19 +103,46 @@
val view = LocalView.current
// When the current dot is changed, we need to update our animations.
- LaunchedEffect(currentDot) {
- view.performHapticFeedback(
- HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
- )
+ LaunchedEffect(currentDot, isAnimationEnabled) {
+ // Perform haptic feedback, but only if the current dot is not null, so we don't perform it
+ // when the UI first shows up or when the user lifts their pointer/finger.
+ if (currentDot != null) {
+ view.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
- // Make sure that the current dot is scaled up while the other dots are scaled back down.
+ if (!isAnimationEnabled) {
+ return@LaunchedEffect
+ }
+
+ // Make sure that the current dot is scaled up while the other dots are scaled back
+ // down.
dotScalingAnimatables.entries.forEach { (dot, animatable) ->
val isSelected = dot == currentDot
- launch {
- animatable.animateTo(if (isSelected) 2f else 1f)
+ // Launch using the longer-lived scope because we want these animations to proceed to
+ // completion even if the LaunchedEffect is canceled because its key objects have
+ // changed.
+ scope.launch {
if (isSelected) {
- animatable.animateTo(1f)
+ animatable.animateTo(
+ targetValue = (SELECTED_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat()),
+ animationSpec =
+ tween(
+ durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS,
+ easing = Easings.StandardAccelerate,
+ ),
+ )
+ } else {
+ animatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS,
+ easing = Easings.StandardDecelerate,
+ ),
+ )
}
}
}
@@ -116,14 +150,18 @@
selectedDots.forEach { dot ->
lineFadeOutAnimatables[dot]?.let { line ->
if (!line.isRunning) {
+ // Launch using the longer-lived scope because we want these animations to
+ // proceed to completion even if the LaunchedEffect is canceled because its key
+ // objects have changed.
scope.launch {
if (dot == currentDot) {
- // Reset the fade-out animation for the current dot. When the current
- // dot is switched, this entire code block runs again for the newly
- // selected dot.
+ // Reset the fade-out animation for the current dot. When the
+ // current dot is switched, this entire code block runs again for
+ // the newly selected dot.
line.snapTo(1f)
} else {
- // For all non-current dots, make sure that the lines are fading out.
+ // For all non-current dots, make sure that the lines are fading
+ // out.
line.animateTo(
targetValue = 0f,
animationSpec =
@@ -139,6 +177,17 @@
}
}
+ // Show the failure animation if the user entered the wrong input.
+ LaunchedEffect(animateFailure) {
+ if (animateFailure) {
+ showFailureAnimation(
+ dots = dots,
+ scalingAnimatables = dotScalingAnimatables,
+ )
+ viewModel.onFailureAnimationShown()
+ }
+ }
+
// This is the position of the input pointer.
var inputPosition: Offset? by remember { mutableStateOf(null) }
@@ -148,27 +197,34 @@
// when it leaves the bounds of the dot grid.
.clipToBounds()
.onSizeChanged { containerSize = it }
- .pointerInput(Unit) {
- detectDragGestures(
- onDragStart = { start ->
- inputPosition = start
- viewModel.onDragStart()
- },
- onDragEnd = {
- inputPosition = null
- lineFadeOutAnimatables.values.forEach { animatable ->
- scope.launch { animatable.animateTo(1f) }
- }
- viewModel.onDragEnd()
- },
- ) { change, _ ->
- inputPosition = change.position
- viewModel.onDrag(
- xPx = change.position.x,
- yPx = change.position.y,
- containerSizePx = containerSize.width,
- verticalOffsetPx = verticalOffset,
- )
+ .thenIf(isInputEnabled) {
+ Modifier.pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = { start ->
+ inputPosition = start
+ viewModel.onDragStart()
+ },
+ onDragEnd = {
+ inputPosition = null
+ if (isAnimationEnabled) {
+ lineFadeOutAnimatables.values.forEach { animatable ->
+ // Launch using the longer-lived scope because we want these
+ // animations to proceed to completion even if the surrounding
+ // scope is canceled.
+ scope.launch { animatable.animateTo(1f) }
+ }
+ }
+ viewModel.onDragEnd()
+ },
+ ) { change, _ ->
+ inputPosition = change.position
+ viewModel.onDrag(
+ xPx = change.position.x,
+ yPx = change.position.y,
+ containerSizePx = containerSize.width,
+ verticalOffsetPx = verticalOffset,
+ )
+ }
}
}
) {
@@ -247,3 +303,62 @@
// farther the user input pointer goes from the line, the more opaque the line gets.
return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f)
}
+
+private suspend fun showFailureAnimation(
+ dots: List<PatternDotViewModel>,
+ scalingAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>,
+) {
+ val dotsByRow =
+ buildList<MutableList<PatternDotViewModel>> {
+ dots.forEach { dot ->
+ val rowIndex = dot.y
+ while (size <= rowIndex) {
+ add(mutableListOf())
+ }
+ get(rowIndex).add(dot)
+ }
+ }
+
+ coroutineScope {
+ dotsByRow.forEachIndexed { rowIndex, rowDots ->
+ rowDots.forEach { dot ->
+ scalingAnimatables[dot]?.let { dotScaleAnimatable ->
+ launch {
+ dotScaleAnimatable.animateTo(
+ targetValue =
+ FAILURE_ANIMATION_DOT_DIAMETER_DP / DOT_DIAMETER_DP.toFloat(),
+ animationSpec =
+ tween(
+ durationMillis =
+ FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS,
+ delayMillis =
+ rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS,
+ easing = Easings.Linear,
+ ),
+ )
+
+ dotScaleAnimatable.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ tween(
+ durationMillis =
+ FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION,
+ easing = Easings.Standard,
+ ),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+private const val DOT_DIAMETER_DP = 16
+private const val SELECTED_DOT_DIAMETER_DP = 24
+private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
+private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750
+private const val LINE_STROKE_WIDTH_DP = 16
+private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13
+private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
+private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
+private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 9c210c2..20b859a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -18,11 +18,15 @@
package com.android.systemui.bouncer.ui.composable
+import android.view.HapticFeedbackConstants
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
@@ -48,6 +52,7 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -55,15 +60,23 @@
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
import com.android.compose.grid.VerticalGrid
import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.thenIf
import kotlin.math.max
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
@Composable
internal fun PinBouncer(
@@ -75,6 +88,16 @@
// The length of the PIN input received so far, so we know how many dots to render.
val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
+ val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+ val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+
+ // Show the failure animation if the user entered the wrong input.
+ LaunchedEffect(animateFailure) {
+ if (animateFailure) {
+ showFailureAnimation()
+ viewModel.onFailureAnimationShown()
+ }
+ }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -116,6 +139,7 @@
val digit = index + 1
PinButton(
onClicked = { viewModel.onPinButtonClicked(digit) },
+ isEnabled = isInputEnabled,
) { contentColor ->
PinDigit(digit, contentColor)
}
@@ -124,7 +148,8 @@
PinButton(
onClicked = { viewModel.onBackspaceButtonClicked() },
onLongPressed = { viewModel.onBackspaceButtonLongPressed() },
- isHighlighted = true,
+ isEnabled = isInputEnabled,
+ isIconButton = true,
) { contentColor ->
PinIcon(
Icon.Resource(
@@ -138,13 +163,15 @@
PinButton(
onClicked = { viewModel.onPinButtonClicked(0) },
+ isEnabled = isInputEnabled,
) { contentColor ->
PinDigit(0, contentColor)
}
PinButton(
onClicked = { viewModel.onAuthenticateButtonClicked() },
- isHighlighted = true,
+ isEnabled = isInputEnabled,
+ isIconButton = true,
) { contentColor ->
PinIcon(
Icon.Resource(
@@ -187,61 +214,107 @@
@Composable
private fun PinButton(
onClicked: () -> Unit,
+ isEnabled: Boolean,
modifier: Modifier = Modifier,
onLongPressed: (() -> Unit)? = null,
- isHighlighted: Boolean = false,
+ isIconButton: Boolean = false,
content: @Composable (contentColor: Color) -> Unit,
) {
var isPressed: Boolean by remember { mutableStateOf(false) }
+
+ val view = LocalView.current
+ LaunchedEffect(isPressed) {
+ if (isPressed) {
+ view.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+ }
+ }
+
+ // Pin button animation specification is asymmetric: fast animation to the pressed state, and a
+ // slow animation upon release. Note that isPressed is guaranteed to be true for at least the
+ // press animation duration (see below in detectTapGestures).
+ val animEasing = if (isPressed) pinButtonPressedEasing else pinButtonReleasedEasing
+ val animDurationMillis =
+ (if (isPressed) pinButtonPressedDuration else pinButtonReleasedDuration).toInt(
+ DurationUnit.MILLISECONDS
+ )
+
val cornerRadius: Dp by
animateDpAsState(
- if (isPressed) 24.dp else PinButtonSize / 2,
+ if (isPressed) 24.dp else pinButtonSize / 2,
label = "PinButton round corners",
+ animationSpec = tween(animDurationMillis, easing = animEasing)
)
+ val colorAnimationSpec: AnimationSpec<Color> = tween(animDurationMillis, easing = animEasing)
val containerColor: Color by
animateColorAsState(
when {
- isPressed -> MaterialTheme.colorScheme.primaryContainer
- isHighlighted -> MaterialTheme.colorScheme.secondaryContainer
- else -> MaterialTheme.colorScheme.surface
+ isPressed -> MaterialTheme.colorScheme.primary
+ isIconButton -> MaterialTheme.colorScheme.secondaryContainer
+ else -> MaterialTheme.colorScheme.surfaceVariant
},
label = "Pin button container color",
+ animationSpec = colorAnimationSpec
)
val contentColor: Color by
animateColorAsState(
when {
- isPressed -> MaterialTheme.colorScheme.onPrimaryContainer
- isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer
- else -> MaterialTheme.colorScheme.onSurface
+ isPressed -> MaterialTheme.colorScheme.onPrimary
+ isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer
+ else -> MaterialTheme.colorScheme.onSurfaceVariant
},
label = "Pin button container color",
+ animationSpec = colorAnimationSpec
)
+ val scope = rememberCoroutineScope()
+
Box(
contentAlignment = Alignment.Center,
modifier =
modifier
- .size(PinButtonSize)
+ .size(pinButtonSize)
.drawBehind {
drawRoundRect(
color = containerColor,
cornerRadius = CornerRadius(cornerRadius.toPx()),
)
}
- .pointerInput(Unit) {
- detectTapGestures(
- onPress = {
- isPressed = true
- tryAwaitRelease()
- isPressed = false
- },
- onTap = { onClicked() },
- onLongPress = onLongPressed?.let { { onLongPressed() } },
- )
+ .thenIf(isEnabled) {
+ Modifier.pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ scope.launch {
+ isPressed = true
+ val minDuration = async {
+ delay(pinButtonPressedDuration + pinButtonHoldTime)
+ }
+ tryAwaitRelease()
+ minDuration.await()
+ isPressed = false
+ }
+ },
+ onTap = { onClicked() },
+ onLongPress = onLongPressed?.let { { onLongPressed() } },
+ )
+ }
},
) {
content(contentColor)
}
}
-private val PinButtonSize = 84.dp
+private fun showFailureAnimation() {
+ // TODO(b/282730134): implement.
+}
+
+private val pinButtonSize = 84.dp
+
+// Pin button motion spec: http://shortn/_9TTIG6SoEa
+private val pinButtonPressedDuration = 100.milliseconds
+private val pinButtonPressedEasing = LinearEasing
+private val pinButtonHoldTime = 33.milliseconds
+private val pinButtonReleasedDuration = 420.milliseconds
+private val pinButtonReleasedEasing = Easings.Standard
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
new file mode 100644
index 0000000..83071d7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.compose.modifiers
+
+import androidx.compose.ui.Modifier
+
+/**
+ * Concatenates this modifier with another if `condition` is true.
+ *
+ * @param condition Whether or not to apply the modifiers.
+ * @param factory Creates the modifier to concatenate with the current one.
+ * @return a Modifier representing this modifier followed by other in sequence.
+ * @see Modifier.then
+ *
+ * This method allows inline conditional addition of modifiers to a modifier chain. Instead of
+ * writing
+ *
+ * ```
+ * val aModifier = Modifier.a()
+ * val bModifier = if(condition) aModifier.b() else aModifier
+ * Composable(modifier = bModifier)
+ * ```
+ *
+ * You can instead write
+ *
+ * ```
+ * Composable(modifier = Modifier.a().thenIf(condition){
+ * Modifier.b()
+ * }
+ * ```
+ *
+ * This makes the modifier chain easier to read.
+ *
+ * Note that unlike the non-factory version, the conditional modifier is recreated each time, and
+ * may never be created at all.
+ */
+inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier =
+ if (condition) this.then(factory()) else this
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 252c8e3..e557c8e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.WeatherData
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -50,6 +51,7 @@
private val layoutInflater: LayoutInflater,
private val resources: Resources,
private val settings: ClockSettings?,
+ private val hasStepClockAnimation: Boolean = false,
) : ClockController {
override val smallClock: DefaultClockFaceController
override val largeClock: LargeClockFaceController
@@ -170,7 +172,8 @@
view: AnimatableClockView,
seedColor: Int?,
) : DefaultClockFaceController(view, seedColor) {
- override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
+ override val config =
+ ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
init {
animations = LargeClockAnimations(view, 0f, 0f)
@@ -225,6 +228,8 @@
clocks.forEach { it.refreshFormat() }
}
+
+ override fun onWeatherDataChanged(data: WeatherData) {}
}
open inner class DefaultClockAnimations(
@@ -271,6 +276,8 @@
// the top margin change in recomputePadding to make clock be centered
view.translationY = 0.5f * view.bottom * (1 - swipingFraction)
}
+
+ override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
}
inner class LargeClockAnimations(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 0fd1b49..949641a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -29,10 +29,11 @@
const val DEFAULT_CLOCK_ID = "DEFAULT"
/** Provides the default system clock */
-class DefaultClockProvider constructor(
+class DefaultClockProvider(
val ctx: Context,
val layoutInflater: LayoutInflater,
- val resources: Resources
+ val resources: Resources,
+ val hasStepClockAnimation: Boolean = false
) : ClockProvider {
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
@@ -42,7 +43,13 @@
throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return DefaultClockController(ctx, layoutInflater, resources, settings)
+ return DefaultClockController(
+ ctx,
+ layoutInflater,
+ resources,
+ settings,
+ hasStepClockAnimation,
+ )
}
override fun getClockThumbnail(id: ClockId): Drawable? {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 5d0a3af..3ae328e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -74,18 +74,10 @@
resources: Resources,
dozeFraction: Float,
foldFraction: Float,
- ) {
- events.onColorPaletteChanged(resources)
- smallClock.animations.doze(dozeFraction)
- largeClock.animations.doze(dozeFraction)
- smallClock.animations.fold(foldFraction)
- largeClock.animations.fold(foldFraction)
- smallClock.events.onTimeTick()
- largeClock.events.onTimeTick()
- }
+ )
/** Optional method for dumping debug information */
- fun dump(pw: PrintWriter) {}
+ fun dump(pw: PrintWriter)
}
/** Interface for a specific clock face version rendered by the clock */
@@ -109,37 +101,37 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call whenever timezone changes */
- fun onTimeZoneChanged(timeZone: TimeZone) {}
+ fun onTimeZoneChanged(timeZone: TimeZone)
/** Call whenever the text time format changes (12hr vs 24hr) */
- fun onTimeFormatChanged(is24Hr: Boolean) {}
+ fun onTimeFormatChanged(is24Hr: Boolean)
/** Call whenever the locale changes */
- fun onLocaleChanged(locale: Locale) {}
+ fun onLocaleChanged(locale: Locale)
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(resources: Resources) {}
+ fun onColorPaletteChanged(resources: Resources)
/** Call if the seed color has changed and should be updated */
- fun onSeedColorChanged(seedColor: Int?) {}
+ fun onSeedColorChanged(seedColor: Int?)
/** Call whenever the weather data should update */
- fun onWeatherDataChanged(data: WeatherData) {}
+ fun onWeatherDataChanged(data: WeatherData)
}
/** Methods which trigger various clock animations */
interface ClockAnimations {
/** Runs an enter animation (if any) */
- fun enter() {}
+ fun enter()
/** Sets how far into AOD the device currently is. */
- fun doze(fraction: Float) {}
+ fun doze(fraction: Float)
/** Sets how far into the folding animation the device is. */
- fun fold(fraction: Float) {}
+ fun fold(fraction: Float)
/** Runs the battery animation (if any). */
- fun charge() {}
+ fun charge()
/**
* Runs when the clock's position changed during the move animation.
@@ -150,32 +142,32 @@
* @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
* it finished moving.
*/
- fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+ fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
/**
* Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
* 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
*/
- fun onPickerCarouselSwiping(swipingFraction: Float) {}
+ fun onPickerCarouselSwiping(swipingFraction: Float)
}
/** Events that have specific data about the related face */
interface ClockFaceEvents {
/** Call every time tick */
- fun onTimeTick() {}
+ fun onTimeTick()
/**
* Region Darkness specific to the clock face.
* - isRegionDark = dark theme -> clock should be light
* - !isRegionDark = light theme -> clock should be dark
*/
- fun onRegionDarknessChanged(isRegionDark: Boolean) {}
+ fun onRegionDarknessChanged(isRegionDark: Boolean)
/**
* Call whenever font settings change. Pass in a target font size in pixels. The specific clock
* design is allowed to ignore this target size on a case-by-case basis.
*/
- fun onFontSettingChanged(fontSizePx: Float) {}
+ fun onFontSettingChanged(fontSizePx: Float)
/**
* Target region information for the clock face. For small clock, this will match the bounds of
@@ -184,7 +176,7 @@
* render within the centered targetRect to avoid obstructing other elements. The specified
* targetRegion is relative to the parent view.
*/
- fun onTargetRegionChanged(targetRegion: Rect?) {}
+ fun onTargetRegionChanged(targetRegion: Rect?)
}
/** Tick rates for clocks */
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e8b9f2a..c619d46 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2973,9 +2973,11 @@
<xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
</string>
- <!-- TODO(b/259369672): Replace with final resource. -->
<!-- [CHAR LIMIT=30] Label used to open Note Task -->
- <string name="note_task_button_label">Notetaking</string>
+ <string name="note_task_button_label">Note-taking</string>
+
+ <!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
+ <string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
<string name="broadcasting_description_is_broadcasting">Broadcasting</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 1db0ab6..835cc13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -46,7 +46,6 @@
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -363,7 +362,7 @@
boolean customClockAnimation = clock != null
&& clock.getLargeClock().getConfig().getHasCustomPositionUpdatedAnimation();
- if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+ if (customClockAnimation) {
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
@@ -400,8 +399,10 @@
@VisibleForTesting
static class SplitShadeTransitionAdapter extends Transition {
private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
+ private static final String PROP_BOUNDS_RIGHT = "splitShadeTransitionAdapter:boundsRight";
private static final String PROP_X_IN_WINDOW = "splitShadeTransitionAdapter:xInWindow";
- private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS_LEFT, PROP_X_IN_WINDOW};
+ private static final String[] TRANSITION_PROPERTIES = {
+ PROP_BOUNDS_LEFT, PROP_BOUNDS_RIGHT, PROP_X_IN_WINDOW};
private final KeyguardClockSwitchController mController;
@@ -412,6 +413,7 @@
private void captureValues(TransitionValues transitionValues) {
transitionValues.values.put(PROP_BOUNDS_LEFT, transitionValues.view.getLeft());
+ transitionValues.values.put(PROP_BOUNDS_RIGHT, transitionValues.view.getRight());
int[] locationInWindowTmp = new int[2];
transitionValues.view.getLocationInWindow(locationInWindowTmp);
transitionValues.values.put(PROP_X_IN_WINDOW, locationInWindowTmp[0]);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 2d0bf9c..d2b10d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -63,7 +63,11 @@
bgDispatcher,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
/* handleAllUsers= */ true,
- new DefaultClockProvider(context, layoutInflater, resources),
+ new DefaultClockProvider(
+ context,
+ layoutInflater,
+ resources,
+ featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION)),
context.getString(R.string.lockscreen_clock_id_fallback),
logBuffer,
/* keepAllLoaded = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 0782537..630cfff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -152,7 +152,7 @@
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
PixelFormat.TRANSLUCENT
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index e462e2f..1d2fce7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -148,12 +148,17 @@
*
* If the input is correct, the device will be unlocked and the lock screen and bouncer will be
* dismissed and hidden.
+ *
+ * @param input The input from the user to try to authenticate with. This can be a list of
+ * different things, based on the current authentication method.
+ * @return `true` if the authentication succeeded and the device is now unlocked; `false`
+ * otherwise.
*/
fun authenticate(
input: List<Any>,
- ) {
+ ): Boolean {
if (repository.throttling.value != null) {
- return
+ return false
}
val isAuthenticated = authenticationInteractor.authenticate(input)
@@ -186,6 +191,8 @@
}
else -> repository.setMessage(errorMessage(authenticationMethod.value))
}
+
+ return isAuthenticated
}
private fun promptMessage(authMethod: AuthenticationMethodModel): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 774a559..d95b70c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -16,14 +16,37 @@
package com.android.systemui.bouncer.ui.viewmodel
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
-sealed interface AuthMethodBouncerViewModel {
+sealed class AuthMethodBouncerViewModel(
/**
* Whether user input is enabled.
*
* If `false`, user input should be completely ignored in the UI as the user is "locked out" of
* being able to attempt to unlock the device.
*/
- val isInputEnabled: StateFlow<Boolean>
+ val isInputEnabled: StateFlow<Boolean>,
+) {
+
+ private val _animateFailure = MutableStateFlow(false)
+ /**
+ * Whether a failure animation should be shown. Once consumed, the UI must call
+ * [onFailureAnimationShown] to consume this state.
+ */
+ val animateFailure: StateFlow<Boolean> = _animateFailure.asStateFlow()
+
+ /**
+ * Notifies that the failure animation has been shown. This should be called to consume a `true`
+ * value in [animateFailure].
+ */
+ fun onFailureAnimationShown() {
+ _animateFailure.value = false
+ }
+
+ /** Ask the UI to show the failure animation. */
+ protected fun showFailureAnimation() {
+ _animateFailure.value = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 02991bd..984d9ab 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -20,6 +20,7 @@
import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
import com.android.systemui.dagger.qualifiers.Application
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -29,6 +30,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -45,21 +47,6 @@
) {
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
- /**
- * Whether updates to the message should be cross-animated from one message to another.
- *
- * If `false`, no animation should be applied, the message text should just be replaced
- * instantly.
- */
- val isMessageUpdateAnimationsEnabled: StateFlow<Boolean> =
- interactor.throttling
- .map { it == null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.throttling.value == null,
- )
-
private val isInputEnabled: StateFlow<Boolean> =
interactor.throttling
.map { it == null }
@@ -104,13 +91,21 @@
)
/** The user-facing message to show in the bouncer. */
- val message: StateFlow<String> =
- interactor.message
- .map { it ?: "" }
+ val message: StateFlow<MessageViewModel> =
+ combine(
+ interactor.message,
+ interactor.throttling,
+ ) { message, throttling ->
+ toMessageViewModel(message, throttling)
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.message.value ?: "",
+ initialValue =
+ toMessageViewModel(
+ message = interactor.message.value,
+ throttling = interactor.throttling.value,
+ ),
)
private val _throttlingDialogMessage = MutableStateFlow<String?>(null)
@@ -177,6 +172,28 @@
}
}
+ private fun toMessageViewModel(
+ message: String?,
+ throttling: AuthenticationThrottledModel?,
+ ): MessageViewModel {
+ return MessageViewModel(
+ text = message ?: "",
+ isUpdateAnimated = throttling == null,
+ )
+ }
+
+ data class MessageViewModel(
+ val text: String,
+
+ /**
+ * Whether updates to the message should be cross-animated from one message to another.
+ *
+ * If `false`, no animation should be applied, the message text should just be replaced
+ * instantly.
+ */
+ val isUpdateAnimated: Boolean,
+ )
+
@AssistedFactory
interface Factory {
fun create(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index c38fcaa..55929b5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -24,8 +24,11 @@
/** Holds UI state and handles user input for the password bouncer UI. */
class PasswordBouncerViewModel(
private val interactor: BouncerInteractor,
- override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+ isInputEnabled: StateFlow<Boolean>,
+) :
+ AuthMethodBouncerViewModel(
+ isInputEnabled = isInputEnabled,
+ ) {
private val _password = MutableStateFlow("")
/** The password entered so far. */
@@ -47,7 +50,10 @@
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
fun onAuthenticateKeyPressed() {
- interactor.authenticate(password.value.toCharArray().toList())
+ if (!interactor.authenticate(password.value.toCharArray().toList())) {
+ showFailureAnimation()
+ }
+
_password.value = ""
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 1b0b38e..d9ef75d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -37,8 +37,11 @@
private val applicationContext: Context,
applicationScope: CoroutineScope,
private val interactor: BouncerInteractor,
- override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+ isInputEnabled: StateFlow<Boolean>,
+) :
+ AuthMethodBouncerViewModel(
+ isInputEnabled = isInputEnabled,
+ ) {
/** The number of columns in the dot grid. */
val columnCount = 3
@@ -150,7 +153,11 @@
/** Notifies that the user has ended the drag gesture across the dot grid. */
fun onDragEnd() {
- interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
+ val isSuccessfullyAuthenticated =
+ interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
+ if (!isSuccessfullyAuthenticated) {
+ showFailureAnimation()
+ }
_dots.value = defaultDots()
_currentDot.value = null
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 2a733d9..5c0fd92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -33,8 +33,11 @@
class PinBouncerViewModel(
private val applicationScope: CoroutineScope,
private val interactor: BouncerInteractor,
- override val isInputEnabled: StateFlow<Boolean>,
-) : AuthMethodBouncerViewModel {
+ isInputEnabled: StateFlow<Boolean>,
+) :
+ AuthMethodBouncerViewModel(
+ isInputEnabled = isInputEnabled,
+ ) {
private val entered = MutableStateFlow<List<Int>>(emptyList())
/**
@@ -92,7 +95,10 @@
/** Notifies that the user clicked the "enter" button. */
fun onAuthenticateButtonClicked() {
- interactor.authenticate(entered.value)
+ if (!interactor.authenticate(entered.value)) {
+ showFailureAnimation()
+ }
+
entered.value = emptyList()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 8ba060e..1eba667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -35,6 +35,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
import java.io.PrintWriter
@@ -67,6 +68,7 @@
@Background private val backgroundExecutor: Executor,
private val serviceListingBuilder: (Context) -> ServiceListing,
private val userTracker: UserTracker,
+ private val activityTaskManagerProxy: ActivityTaskManagerProxy,
dumpManager: DumpManager,
private val featureFlags: FeatureFlags
) : ControlsListingController, Dumpable {
@@ -76,9 +78,18 @@
context: Context,
@Background executor: Executor,
userTracker: UserTracker,
+ activityTaskManagerProxy: ActivityTaskManagerProxy,
dumpManager: DumpManager,
featureFlags: FeatureFlags
- ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags)
+ ) : this(
+ context,
+ executor,
+ ::createServiceListing,
+ userTracker,
+ activityTaskManagerProxy,
+ dumpManager,
+ featureFlags
+ )
private var serviceListing = serviceListingBuilder(context)
// All operations in background thread
@@ -113,7 +124,8 @@
}
private fun updateServices(newServices: List<ControlsServiceInfo>) {
- if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS) &&
+ activityTaskManagerProxy.supportsMultiWindow(context)) {
val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
newServices.forEach {
it.resolvePanelActivity(allowAllApps) }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e99db14..870fff7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -355,7 +355,8 @@
// TODO(b/280426085): Tracking Bug
@JvmField
- val NEW_BLUETOOTH_REPOSITORY = unreleasedFlag(612, "new_bluetooth_repository")
+ val NEW_BLUETOOTH_REPOSITORY =
+ unreleasedFlag(612, "new_bluetooth_repository", teamfood = true)
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index 356a8fb..4dad179 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -47,7 +47,7 @@
class KeyguardQuickAffordanceLocalUserSelectionManager
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
broadcastDispatcher: BroadcastDispatcher,
@@ -126,6 +126,11 @@
}
override fun getSelections(): Map<String, List<String>> {
+ // If the custom shortcuts feature is not enabled, ignore prior selections and use defaults
+ if (!context.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) {
+ return defaults
+ }
+
val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
val result =
slotKeys
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 4ba2eb9..1227078 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
-import android.content.Intent
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -33,7 +32,6 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
@@ -103,17 +101,6 @@
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
!walletController.isWalletEnabled || queryCards().isEmpty() -> {
- val componentName =
- walletController.walletClient.createWalletSettingsIntent().toComponentName()
- val actionText =
- if (componentName != null) {
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_action_template,
- pickerName,
- )
- } else {
- null
- }
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
@@ -124,8 +111,6 @@
R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
),
),
- actionText = actionText,
- actionComponentName = componentName,
)
}
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
@@ -182,14 +167,6 @@
}
}
- private fun Intent?.toComponentName(): String? {
- if (this == null) {
- return null
- }
-
- return componentName(packageName = `package`, action = action)
- }
-
companion object {
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 641e20b..16ad29a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -21,12 +21,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
@@ -35,6 +40,7 @@
@Inject
constructor(
private val secureSettings: SecureSettings,
+ private val clockRegistry: ClockRegistry,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
@@ -47,6 +53,24 @@
.onStart { emit(Unit) } // Forces an initial update.
.map { getClockSize() }
+ val currentClockId: Flow<ClockId> =
+ callbackFlow {
+ fun send() {
+ trySend(clockRegistry.currentClockId)
+ }
+
+ val listener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() {
+ send()
+ }
+ }
+ clockRegistry.registerClockChangeListener(listener)
+ send()
+ awaitClose { clockRegistry.unregisterClockChangeListener(listener) }
+ }
+ .mapNotNull { it }
+
private suspend fun getClockSize(): SettingsClockSize {
return withContext(backgroundDispatcher) {
if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 98f445c..dad5831 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.ClockId
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -31,4 +32,6 @@
repository: KeyguardClockRepository,
) {
val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
+
+ val currentClockId: Flow<ClockId> = repository.currentClockId
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index c8d37a1..a8d662c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -406,19 +406,21 @@
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
if (viewModel.useLongPress) {
- view.setOnTouchListener(
- KeyguardQuickAffordanceOnTouchListener(
- view,
- viewModel,
- messageDisplayer,
- vibratorHelper,
- falsingManager,
- )
+ val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
+ view,
+ viewModel,
+ messageDisplayer,
+ vibratorHelper,
+ falsingManager,
)
+ view.setOnTouchListener(onTouchListener)
+ view.onLongClickListener =
+ OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
}
} else {
+ view.onLongClickListener = null
view.setOnClickListener(null)
view.setOnTouchListener(null)
}
@@ -454,6 +456,42 @@
.start()
}
+ private class OnLongClickListener(
+ private val falsingManager: FalsingManager?,
+ private val viewModel: KeyguardQuickAffordanceViewModel,
+ private val vibratorHelper: VibratorHelper?,
+ private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
+ ) : View.OnLongClickListener {
+ override fun onLongClick(view: View): Boolean {
+ if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
+ return true
+ }
+
+ if (viewModel.configKey != null) {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ expandable = Expandable.fromView(view),
+ slotId = viewModel.slotId,
+ )
+ )
+ vibratorHelper?.vibrate(
+ if (viewModel.isActivated) {
+ KeyguardBottomAreaVibrations.Activated
+ } else {
+ KeyguardBottomAreaVibrations.Deactivated
+ }
+ )
+ }
+
+ onTouchListener.cancel()
+ return true
+ }
+
+ override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+
+ }
+
private class OnClickListener(
private val viewModel: KeyguardQuickAffordanceViewModel,
private val falsingManager: FalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 57c32b3..1b5b329 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -21,19 +21,17 @@
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
-/** Binder for the small clock view, large clock view and smartspace. */
-object KeyguardPreviewClockSmartspaceViewBinder {
+/** Binder for the small clock view, large clock view. */
+object KeyguardPreviewClockViewBinder {
@JvmStatic
fun bind(
largeClockHostView: View,
smallClockHostView: View,
- smartspace: View?,
- viewModel: KeyguardPreviewClockSmartspaceViewModel,
+ viewModel: KeyguardPreviewClockViewModel,
) {
largeClockHostView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -46,15 +44,5 @@
viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
}
}
-
- smartspace?.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) }
- }
- }
- }
-
- private fun View.setTopPadding(padding: Int) {
- setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
new file mode 100644
index 0000000..f5e4c6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Binder for the small clock view, large clock view and smartspace. */
+object KeyguardPreviewSmartspaceViewBinder {
+
+ @JvmStatic
+ fun bind(
+ smartspace: View,
+ viewModel: KeyguardPreviewSmartspaceViewModel,
+ ) {
+ smartspace.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } }
+
+ launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } }
+ }
+ }
+ }
+
+ private fun View.setTopPadding(padding: Int) {
+ setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
index 5745d6a..7685345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -46,7 +46,7 @@
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean {
return when (event.actionMasked) {
- MotionEvent.ACTION_DOWN ->
+ MotionEvent.ACTION_DOWN -> {
if (viewModel.configKey != null) {
downDisplayCoords.set(event.rawX, event.rawY)
if (isUsingAccurateTool(event)) {
@@ -62,21 +62,10 @@
.scaleX(PRESSED_SCALE)
.scaleY(PRESSED_SCALE)
.setDuration(longPressDurationMs)
- .withEndAction {
- if (
- falsingManager?.isFalseLongTap(
- FalsingManager.MODERATE_PENALTY
- ) == false
- ) {
- dispatchClick(viewModel.configKey)
- }
- cancel()
- }
}
- true
- } else {
- false
}
+ false
+ }
MotionEvent.ACTION_MOVE -> {
if (!isUsingAccurateTool(event)) {
// Moving too far while performing a long-press gesture cancels that
@@ -91,7 +80,7 @@
cancel()
}
}
- true
+ false
}
MotionEvent.ACTION_UP -> {
if (isUsingAccurateTool(event)) {
@@ -146,7 +135,7 @@
}
)
}
- true
+ false
}
MotionEvent.ACTION_CANCEL -> {
cancel()
@@ -179,7 +168,7 @@
view.setOnClickListener(null)
}
- private fun cancel(onAnimationEnd: Runnable? = null) {
+ fun cancel(onAnimationEnd: Runnable? = null) {
longPressAnimator?.cancel()
longPressAnimator = null
view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4308d84..fe62bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -42,9 +42,11 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
import com.android.systemui.plugins.ClockController
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.DefaultClockController
@@ -65,7 +67,8 @@
@Application private val context: Context,
@Main private val mainDispatcher: CoroutineDispatcher,
@Main private val mainHandler: Handler,
- private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel,
+ private val clockViewModel: KeyguardPreviewClockViewModel,
+ private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
@@ -129,16 +132,18 @@
setUpBottomArea(rootView)
setUpSmartspace(rootView)
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ }
setUpUdfps(rootView)
if (!shouldHideClock) {
setUpClock(rootView)
- KeyguardPreviewClockSmartspaceViewBinder.bind(
+ KeyguardPreviewClockViewBinder.bind(
largeClockHostView,
smallClockHostView,
- smartSpaceView,
- clockSmartspaceViewModel,
+ clockViewModel,
)
}
@@ -219,8 +224,8 @@
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
- KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
- context.resources
+ KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
+ context.resources,
)
val startPadding: Int =
@@ -235,7 +240,7 @@
smartSpaceView?.let {
it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
it.isClickable = false
-
+ it.isInvisible = true
parentView.addView(
it,
FrameLayout.LayoutParams(
@@ -372,7 +377,7 @@
resources.getDimensionPixelSize(R.dimen.small_clock_height)
)
layoutParams.topMargin =
- KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) +
+ KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
hostView.layoutParams = layoutParams
@@ -394,9 +399,6 @@
updateLargeClock(clock)
updateSmallClock(clock)
-
- // Hide smart space if the clock has weather display; otherwise show it
- hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
}
private fun updateLargeClock(clock: ClockController) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
new file mode 100644
index 0000000..5301302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View model for the small clock view, large clock view. */
+class KeyguardPreviewClockViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: KeyguardClockInteractor,
+) {
+
+ val isLargeClockVisible: Flow<Boolean> =
+ interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
+
+ val isSmallClockVisible: Flow<Boolean> =
+ interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 00c603b..bf51976 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -24,23 +24,18 @@
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-/** View model for the small clock view, large clock view and smartspace. */
-class KeyguardPreviewClockSmartspaceViewModel
+/** View model for the smartspace. */
+class KeyguardPreviewSmartspaceViewModel
@Inject
constructor(
@Application private val context: Context,
interactor: KeyguardClockInteractor,
) {
- val isLargeClockVisible: Flow<Boolean> =
- interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
-
- val isSmallClockVisible: Flow<Boolean> =
- interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
-
- val smartSpaceTopPadding: Flow<Int> =
+ val smartspaceTopPadding: Flow<Int> =
interactor.selectedClockSize.map {
when (it) {
SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
@@ -48,6 +43,22 @@
}
}
+ val shouldHideSmartspace: Flow<Boolean> =
+ combine(
+ interactor.selectedClockSize,
+ interactor.currentClockId,
+ ::Pair,
+ )
+ .map { (size, currentClockId) ->
+ when (size) {
+ // TODO (b/284122375) This is temporary. We should use clockController
+ // .largeClock.config.hasCustomWeatherDataDisplay instead, but
+ // ClockRegistry.createCurrentClock is not reliable.
+ SettingsClockSize.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+ SettingsClockSize.SMALL -> false
+ }
+ }
+
companion object {
fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
return with(resources) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c9c2ea2..f6a2f37 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -29,6 +29,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -203,14 +204,17 @@
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
}
+ // Using application context for the dialog, instead of the activity context, so we get
+ // the correct screen width when in split screen.
+ Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
- mDialog = new MediaProjectionPermissionDialog(this, () -> {
+ mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> {
ScreenShareOption selectedOption =
((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
grantMediaProjectionPermission(selectedOption.getMode());
}, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
} else {
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
R.style.Theme_SystemUI_Dialog)
.setTitle(dialogTitle)
.setIcon(R.drawable.ic_media_projection_permission)
@@ -263,7 +267,10 @@
final UserHandle hostUserHandle = getHostUserHandle();
if (mScreenCaptureDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(hostUserHandle)) {
- AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
+ // Using application context for the dialog, instead of the activity context, so we get
+ // the correct screen width when in split screen.
+ Context dialogContext = getApplicationContext();
+ AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
setUpDialog(dialog);
dialog.show();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 441b9f5..a2ebead 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -19,6 +19,7 @@
import android.app.role.RoleManager
import android.app.role.RoleManager.ROLE_NOTES
import android.content.Context
+import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.os.PersistableBundle
@@ -42,20 +43,41 @@
context: Context,
user: UserHandle,
): ShortcutInfo {
+ val packageName = getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+
val extras = PersistableBundle()
- getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
+ if (packageName != null) {
// Set custom app badge using the icon from ROLES_NOTES default app.
extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
}
+ val shortLabel = context.getString(R.string.note_task_button_label)
+
+ val applicationLabel = context.packageManager.getApplicationLabel(packageName)
+ val longLabel =
+ if (applicationLabel == null) {
+ shortLabel
+ } else {
+ context.getString(
+ R.string.note_task_shortcut_long_label,
+ applicationLabel,
+ )
+ }
+
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
.setIntent(LaunchNoteTaskActivity.newIntent(context = context))
- .setShortLabel(context.getString(R.string.note_task_button_label))
+ .setShortLabel(shortLabel)
+ .setLongLabel(longLabel)
.setLongLived(true)
.setIcon(icon)
.setExtras(extras)
.build()
}
+
+ private fun PackageManager.getApplicationLabel(packageName: String?): String? =
+ runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+ .getOrNull()
+ ?.let { info -> getApplicationLabel(info).toString() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 380b85c..212a2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -286,7 +286,6 @@
mHandler.removeCallbacks(mHideProgressBarRunnable);
mHandler.removeCallbacks(mHideSearchingRunnable);
mMobileNetworkLayout.setOnClickListener(null);
- mMobileDataToggle.setOnCheckedChangeListener(null);
mConnectedWifListLayout.setOnClickListener(null);
if (mSecondaryMobileNetworkLayout != null) {
mSecondaryMobileNetworkLayout.setOnClickListener(null);
@@ -351,18 +350,16 @@
}
mInternetDialogController.connectCarrierNetwork();
});
- mMobileDataToggle.setOnCheckedChangeListener(
- (buttonView, isChecked) -> {
- if (!isChecked && shouldShowMobileDialog()) {
- showTurnOffMobileDialog();
- } else if (!shouldShowMobileDialog()) {
- if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
- return;
- }
- mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
- isChecked, false);
- }
- });
+ mMobileDataToggle.setOnClickListener(v -> {
+ boolean isChecked = mMobileDataToggle.isChecked();
+ if (!isChecked && shouldShowMobileDialog()) {
+ mMobileDataToggle.setChecked(true);
+ showTurnOffMobileDialog();
+ } else if (mInternetDialogController.isMobileDataEnabled() != isChecked) {
+ mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+ isChecked, false);
+ }
+ });
mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
mWiFiToggle.setOnCheckedChangeListener(
@@ -696,9 +693,7 @@
mAlertDialog = new Builder(mContext)
.setTitle(R.string.mobile_data_disable_title)
.setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
- .setNegativeButton(android.R.string.cancel, (d, w) -> {
- mMobileDataToggle.setChecked(true);
- })
+ .setNegativeButton(android.R.string.cancel, (d, w) -> {})
.setPositiveButton(
com.android.internal.R.string.alert_windows_notification_turn_off_action,
(d, w) -> {
@@ -708,7 +703,6 @@
Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
})
.create();
- mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
SystemUIDialog.registerDismissListener(mAlertDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index bfaf3d0..604d449 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -93,6 +93,7 @@
}
dismiss()
}
+ setCancelButtonOnClickListener { dismiss() }
initRecordOptionsView()
}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 10e2afe..9a1ffcb 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -203,7 +203,10 @@
}
public void setBottomEdgeRadius(float radius) {
- mBottomEdgeRadius = radius;
+ if (mBottomEdgeRadius != radius) {
+ mBottomEdgeRadius = radius;
+ invalidateSelf();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 784a360..047fea1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2206,6 +2206,7 @@
// TODO: non-linearly transform progress fraction into squish amount (ease-in, linear out)
mCurrentBackProgress = progressFraction;
applyBackScaling(progressFraction);
+ mQsController.setClippingBounds();
}
/** Resets back progress. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8672260..c42c2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -114,6 +114,8 @@
public class QuickSettingsController implements Dumpable {
public static final String TAG = "QuickSettingsController";
+ public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
+
private QS mQs;
private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
@@ -1128,7 +1130,7 @@
* Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
* as well based on the bounds of the shade and QS state.
*/
- private void setClippingBounds() {
+ void setClippingBounds() {
float qsExpansionFraction = computeExpansionFraction();
final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
// Split shade has no QQS
@@ -1216,7 +1218,10 @@
? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
Math.min(top / (float) mScrimCornerRadius, 1f));
- mScrimController.setNotificationBottomRadius(radius);
+
+ float bottomRadius = mExpanded ? screenCornerRadius :
+ calculateBottomCornerRadius(screenCornerRadius);
+ mScrimController.setNotificationBottomRadius(bottomRadius);
}
if (isQsFragmentCreated()) {
float qsTranslation = 0;
@@ -1279,6 +1284,28 @@
nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
}
+ /**
+ * Bottom corner radius should follow screen corner radius unless
+ * predictive back is running. We want a smooth transition from screen
+ * corner radius to scrim corner radius as the notification scrim is scaled down,
+ * but the transition should be brief enough to accommodate very short back gestures.
+ */
+ @VisibleForTesting
+ int calculateBottomCornerRadius(float screenCornerRadius) {
+ return (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
+ Math.min(calculateBottomRadiusProgress(), 1f));
+ }
+
+ @VisibleForTesting
+ float calculateBottomRadiusProgress() {
+ return (1 - mScrimController.getBackScaling()) * SHADE_BACK_ANIM_SCALE_MULTIPLIER;
+ }
+
+ @VisibleForTesting
+ int getScrimCornerRadius() {
+ return mScrimCornerRadius;
+ }
+
void setDisplayInsets(int leftInset, int rightInset) {
mDisplayLeftInset = leftInset;
mDisplayRightInset = rightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 53c40b8..cab95c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -500,7 +500,6 @@
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final PluginDependencyProvider mPluginDependencyProvider;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
private final ExtensionController mExtensionController;
private final UserInfoControllerImpl mUserInfoControllerImpl;
private final DemoModeController mDemoModeController;
@@ -787,7 +786,6 @@
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
PluginDependencyProvider pluginDependencyProvider,
- KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
PhoneStatusBarPolicy phoneStatusBarPolicy,
@@ -884,7 +882,6 @@
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
- mKeyguardDismissUtil = keyguardDismissUtil;
mExtensionController = extensionController;
mUserInfoControllerImpl = userInfoControllerImpl;
mIconPolicy = phoneStatusBarPolicy;
@@ -1742,7 +1739,6 @@
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
- mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked);
Trace.endSection();
}
@@ -2360,15 +2356,6 @@
mNotificationsController.resetUserExpandedStates();
}
- private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
- boolean afterKeyguardGone) {
- if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
- mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
- }
- mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
- afterKeyguardGone /* afterKeyguardGone */);
- }
-
/**
* Notify the shade controller that the current user changed
*
@@ -2838,7 +2825,11 @@
}
private void updateDozingState() {
- Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "Dozing", 0);
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "Dozing", String.valueOf(mDozing),
+ 0);
+ }
Trace.beginSection("CentralSurfaces#updateDozingState");
boolean keyguardVisible = mKeyguardStateController.isVisible();
@@ -3187,6 +3178,10 @@
@Override
public void onStartedWakingUp() {
+ // Between onStartedWakingUp() and onFinishedWakingUp(), the system is changing the
+ // display power mode. To avoid jank, animations should NOT run during these power
+ // mode transitions, which means that whenever possible, animations should
+ // start running during the onFinishedWakingUp() callback instead of this callback.
String tag = "CentralSurfaces#onStartedWakingUp";
DejankUtils.startDetectingBlockingIpcs(tag);
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
@@ -3231,6 +3226,14 @@
updateVisibleToUser();
updateIsKeyguard();
+ });
+ DejankUtils.stopDetectingBlockingIpcs(tag);
+ }
+
+ @Override
+ public void onFinishedWakingUp() {
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ // stopDozing() starts the LOCKSCREEN_TRANSITION_FROM_AOD animation.
mDozeServiceHost.stopDozing();
// This is intentionally below the stopDozing call above, since it avoids that we're
// unnecessarily animating the wakeUp transition. Animations should only be enabled
@@ -3244,13 +3247,7 @@
if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
mShadeController.makeExpandedInvisible();
}
-
});
- DejankUtils.stopDetectingBlockingIpcs(tag);
- }
-
- @Override
- public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false, false);
if (mKeyguardStateController.isOccluded()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 27b68f2..1c90c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -16,48 +16,41 @@
package com.android.systemui.statusbar.phone;
-import android.util.Log;
-
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
/**
- * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
- * implementation passed via {@link #setDismissHandler}.
+ * Executes actions that require the screen to be unlocked.
*/
@SysUISingleton
public class KeyguardDismissUtil implements KeyguardDismissHandler {
- private static final String TAG = "KeyguardDismissUtil";
+ private final KeyguardStateController mKeyguardStateController;
- private volatile KeyguardDismissHandler mDismissHandler;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+
+ private final ActivityStarter mActivityStarter;
@Inject
- public KeyguardDismissUtil() {
+ public KeyguardDismissUtil(KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter) {
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mActivityStarter = activityStarter;
}
- /** Sets the actual {@link KeyguardDismissHandler} implementation. */
- public void setDismissHandler(KeyguardDismissHandler dismissHandler) {
- mDismissHandler = dismissHandler;
- }
-
- /**
- * Executes an action that requires the screen to be unlocked.
- *
- * <p>Must be called after {@link #setDismissHandler}.
- *
- * @param requiresShadeOpen does the shade need to be forced open when hiding the keyguard?
- */
@Override
public void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
boolean afterKeyguardGone) {
- KeyguardDismissHandler dismissHandler = mDismissHandler;
- if (dismissHandler == null) {
- Log.wtf(TAG, "KeyguardDismissHandler not set.");
- action.onDismiss();
- return;
+ if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
+ mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
- dismissHandler.executeWhenUnlocked(action, requiresShadeOpen, afterKeyguardGone);
+ mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
+ afterKeyguardGone /* afterKeyguardGone */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fdb772b..25ecf1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -602,6 +602,10 @@
mNotificationsScrim.setScaleY(scale);
}
+ public float getBackScaling() {
+ return mNotificationsScrim.getScaleY();
+ }
+
public void onTrackingStarted() {
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7d0e3b3..a34cc96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -495,6 +495,7 @@
return mKeyguardStateController.isShowing()
&& !primaryBouncerIsOrWillBeShowing()
+ && !mKeyguardStateController.isKeyguardGoingAway()
&& isUserTrackingStarted
&& !hideBouncerOverDream
&& !mKeyguardStateController.isOccluded()
diff --git a/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt
new file mode 100644
index 0000000..6e82cf6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ActivityTaskManagerProxy.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.app.ActivityTaskManager
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Proxy for static calls to [ActivityTaskManager]. */
+@SysUISingleton
+class ActivityTaskManagerProxy @Inject constructor() {
+
+ /** Calls [ActivityTaskManager.supportsMultiWindow] */
+ fun supportsMultiWindow(context: Context) = ActivityTaskManager.supportsMultiWindow(context)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 9f5c181..374c28d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -82,7 +82,7 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
// Wrong input.
- underTest.authenticate(listOf(9, 8, 7))
+ assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -90,7 +90,7 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
// Correct input.
- underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -114,7 +114,7 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
// Wrong input.
- underTest.authenticate("alohamora".toList())
+ assertThat(underTest.authenticate("alohamora".toList())).isFalse()
assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -122,7 +122,7 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
// Correct input.
- underTest.authenticate("password".toList())
+ assertThat(underTest.authenticate("password".toList())).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -146,9 +146,12 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
// Wrong input.
- underTest.authenticate(
- listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
- )
+ assertThat(
+ underTest.authenticate(
+ listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
+ )
+ )
+ .isFalse()
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -156,7 +159,7 @@
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
// Correct input.
- underTest.authenticate(emptyList())
+ assertThat(underTest.authenticate(emptyList())).isTrue()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -214,7 +217,7 @@
assertThat(isUnlocked).isFalse()
repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
// Wrong PIN.
- underTest.authenticate(listOf(6, 7, 8, 9))
+ assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
}
@@ -223,7 +226,7 @@
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
// Correct PIN, but throttled, so doesn't unlock:
- underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
assertThat(isUnlocked).isFalse()
assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
@@ -241,7 +244,7 @@
assertThat(isUnlocked).isFalse()
// Correct PIN and no longer throttled so unlocks:
- underTest.authenticate(listOf(1, 2, 3, 4))
+ assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
assertThat(isUnlocked).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
new file mode 100644
index 0000000..1642410
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthMethodBouncerViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ utils.authenticationRepository(),
+ )
+ private val underTest =
+ PinBouncerViewModel(
+ applicationScope = testScope.backgroundScope,
+ interactor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ ),
+ isInputEnabled = MutableStateFlow(true),
+ )
+
+ @Test
+ fun animateFailure() =
+ testScope.runTest {
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ val animateFailure by collectLastValue(underTest.animateFailure)
+ assertThat(animateFailure).isFalse()
+
+ // Wrong PIN:
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+ underTest.onPinButtonClicked(5)
+ underTest.onPinButtonClicked(6)
+ underTest.onAuthenticateButtonClicked()
+ assertThat(animateFailure).isTrue()
+
+ underTest.onFailureAnimationShown()
+ assertThat(animateFailure).isFalse()
+
+ // Correct PIN:
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+ underTest.onAuthenticateButtonClicked()
+ assertThat(animateFailure).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index b942ccb..e8c946c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -93,22 +93,21 @@
}
@Test
- fun isMessageUpdateAnimationsEnabled() =
+ fun message() =
testScope.runTest {
- val isMessageUpdateAnimationsEnabled by
- collectLastValue(underTest.isMessageUpdateAnimationsEnabled)
+ val message by collectLastValue(underTest.message)
val throttling by collectLastValue(bouncerInteractor.throttling)
authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+ assertThat(message?.isUpdateAnimated).isTrue()
repeat(BouncerInteractor.THROTTLE_EVERY) {
// Wrong PIN.
bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
}
- assertThat(isMessageUpdateAnimationsEnabled).isFalse()
+ assertThat(message?.isUpdateAnimated).isFalse()
throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
- assertThat(isMessageUpdateAnimationsEnabled).isTrue()
+ assertThat(message?.isUpdateAnimated).isTrue()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index b7b90de..f436aa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -85,7 +85,7 @@
underTest.onShown()
- assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD)
+ assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
assertThat(password).isEqualTo("")
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -109,7 +109,7 @@
underTest.onPasswordInputChanged("password")
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
assertThat(password).isEqualTo("password")
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -156,7 +156,7 @@
underTest.onAuthenticateKeyPressed()
assertThat(password).isEqualTo("")
- assertThat(message).isEqualTo(WRONG_PASSWORD)
+ assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@@ -179,13 +179,13 @@
underTest.onPasswordInputChanged("wrong")
underTest.onAuthenticateKeyPressed()
assertThat(password).isEqualTo("")
- assertThat(message).isEqualTo(WRONG_PASSWORD)
+ assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
// Enter the correct password:
underTest.onPasswordInputChanged("password")
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
underTest.onAuthenticateKeyPressed()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index b588ba2..d7d7154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -89,7 +89,7 @@
underTest.onShown()
- assertThat(message).isEqualTo(ENTER_YOUR_PATTERN)
+ assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
assertThat(isUnlocked).isFalse()
@@ -115,7 +115,7 @@
underTest.onDragStart()
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
assertThat(isUnlocked).isFalse()
@@ -202,7 +202,7 @@
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
- assertThat(message).isEqualTo(WRONG_PATTERN)
+ assertThat(message?.text).isEqualTo(WRONG_PATTERN)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@@ -235,7 +235,7 @@
underTest.onDragEnd()
assertThat(selectedDots).isEmpty()
assertThat(currentDot).isNull()
- assertThat(message).isEqualTo(WRONG_PATTERN)
+ assertThat(message?.text).isEqualTo(WRONG_PATTERN)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 83f9687..3bdaf05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -94,7 +94,7 @@
underTest.onShown()
- assertThat(message).isEqualTo(ENTER_YOUR_PIN)
+ assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
assertThat(pinLengths).isEqualTo(0 to 0)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -116,7 +116,7 @@
underTest.onPinButtonClicked(1)
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
assertThat(pinLengths).isEqualTo(0 to 1)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -140,7 +140,7 @@
underTest.onBackspaceButtonClicked()
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
assertThat(pinLengths).isEqualTo(1 to 0)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -170,7 +170,7 @@
advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
}
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
assertThat(pinLengths).isEqualTo(1 to 0)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -220,7 +220,7 @@
underTest.onAuthenticateButtonClicked()
assertThat(pinLengths).isEqualTo(0 to 0)
- assertThat(message).isEqualTo(WRONG_PIN)
+ assertThat(message?.text).isEqualTo(WRONG_PIN)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@@ -244,7 +244,7 @@
underTest.onPinButtonClicked(4)
underTest.onPinButtonClicked(5) // PIN is now wrong!
underTest.onAuthenticateButtonClicked()
- assertThat(message).isEqualTo(WRONG_PIN)
+ assertThat(message?.text).isEqualTo(WRONG_PIN)
assertThat(pinLengths).isEqualTo(0 to 0)
assertThat(isUnlocked).isFalse()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -254,7 +254,7 @@
underTest.onPinButtonClicked(2)
underTest.onPinButtonClicked(3)
underTest.onPinButtonClicked(4)
- assertThat(message).isEmpty()
+ assertThat(message?.text).isEmpty()
underTest.onAuthenticateButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 10bfc1b..ee213f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
import com.android.systemui.flags.Flags.USE_APP_PANELS
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
@@ -88,6 +89,8 @@
private lateinit var packageManager: PackageManager
@Mock
private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
private var componentName = ComponentName("pkg", "class1")
private var activityName = ComponentName("pkg", "activity")
@@ -112,6 +115,7 @@
// Return disabled by default
`when`(packageManager.getComponentEnabledSetting(any()))
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+ `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true)
mContext.setMockPackageManager(packageManager)
mContext.orCreateTestableResources
@@ -136,6 +140,7 @@
executor,
{ mockSL },
userTracker,
+ activityTaskManagerProxy,
dumpManager,
featureFlags
)
@@ -171,6 +176,7 @@
exec,
{ mockServiceListing },
userTracker,
+ activityTaskManagerProxy,
dumpManager,
featureFlags
)
@@ -637,7 +643,34 @@
assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
}
+ @Test
+ fun testNoPanelIfMultiWindowNotSupported() {
+ `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false)
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ enabled = true,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
private fun ServiceInfo(
componentName: ComponentName,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 9200d72..de3bb6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -72,6 +72,8 @@
val resources: Resources = mock()
whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
.thenReturn(emptyArray())
+ whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled))
+ .thenReturn(true)
whenever(context.resources).thenReturn(resources)
testDispatcher = UnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index bad4b36..b2528c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -66,6 +66,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
sharedPrefs = mutableMapOf()
whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
val userId = it.arguments[2] as Int
@@ -86,6 +87,13 @@
@After
fun tearDown() {
+ mContext
+ .getOrCreateTestableResources()
+ .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
+ mContext
+ .getOrCreateTestableResources()
+ .removeOverride(R.array.config_keyguardQuickAffordanceDefaults)
+
Dispatchers.resetMain()
}
@@ -358,6 +366,22 @@
job.cancel()
}
+ @Test
+ fun getSelections_alwaysReturnsDefaultsIfCustomShortcutsFeatureDisabled() {
+ overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, false)
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2")
+ )
+
+ assertThat(underTest.getSelections()).isEqualTo(
+ mapOf(
+ "leftTest" to listOf("testShortcut1"),
+ "rightTest" to listOf("testShortcut2"),
+ )
+ )
+ }
+
private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 8dc04bd..ca7c5db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -47,6 +47,7 @@
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,6 +71,7 @@
@Before
fun setUp() {
+ overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
context.resources.configuration.setLayoutDirection(Locale.US)
config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
@@ -137,6 +139,13 @@
)
}
+ @After
+ fun tearDown() {
+ mContext
+ .getOrCreateTestableResources()
+ .removeOverride(R.bool.custom_lockscreen_shortcuts_enabled)
+ }
+
@Test
fun setSelections() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 5d2c3ed..895c1cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -102,6 +102,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
@@ -200,7 +202,7 @@
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
backgroundDispatcher = testDispatcher,
- appContext = mContext,
+ appContext = context,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 079ef37..204077b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -36,7 +36,6 @@
import android.content.pm.PackageManager
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.content.pm.UserInfo
import android.graphics.drawable.Icon
@@ -62,6 +61,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
@@ -115,7 +115,11 @@
whenever(context.getString(R.string.note_task_button_label))
.thenReturn(NOTE_TASK_SHORT_LABEL)
+ whenever(context.getString(eq(R.string.note_task_shortcut_long_label), any()))
+ .thenReturn(NOTE_TASK_LONG_LABEL)
whenever(context.packageManager).thenReturn(packageManager)
+ whenever(packageManager.getApplicationInfo(any(), any<Int>())).thenReturn(mock())
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(NOTE_TASK_LONG_LABEL)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(NOTE_TASK_INFO)
whenever(userManager.isUserUnlocked).thenReturn(true)
whenever(userManager.isUserUnlocked(any<Int>())).thenReturn(true)
@@ -706,19 +710,20 @@
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
verify(shortcutManager, never()).disableShortcuts(any())
verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
- val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
- verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
- val actualShortcut = actualShortcuts.value.first()
- assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
- assertThat(actualShortcut.intent).run {
- hasComponentClass(LaunchNoteTaskActivity::class.java)
- hasAction(ACTION_CREATE_NOTE)
+ val shortcutInfo = withArgCaptor { verify(shortcutManager).updateShortcuts(capture()) }
+ with(shortcutInfo.first()) {
+ assertThat(id).isEqualTo(SHORTCUT_ID)
+ assertThat(intent).run {
+ hasComponentClass(LaunchNoteTaskActivity::class.java)
+ hasAction(ACTION_CREATE_NOTE)
+ }
+ assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
+ assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
+ assertThat(isLongLived).isEqualTo(true)
+ assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+ assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
+ .isEqualTo(NOTE_TASK_PACKAGE_NAME)
}
- assertThat(actualShortcut.shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
- assertThat(actualShortcut.isLongLived).isEqualTo(true)
- assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
- assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
- .isEqualTo(NOTE_TASK_PACKAGE_NAME)
}
@Test
@@ -893,7 +898,8 @@
// endregion
private companion object {
- const val NOTE_TASK_SHORT_LABEL = "Notetaking"
+ const val NOTE_TASK_SHORT_LABEL = "Note-taking"
+ const val NOTE_TASK_LONG_LABEL = "Note-taking, App"
const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
const val NOTE_TASK_PACKAGE_NAME = "com.android.note.app"
const val NOTE_TASK_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 3def6ba..f2812b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,9 +16,11 @@
package com.android.systemui.screenrecord;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -32,6 +34,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -41,7 +44,9 @@
import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -77,20 +82,38 @@
private UserContextProvider mUserContextTracker;
@Captor
private ArgumentCaptor<Runnable> mRunnableCaptor;
- private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
- public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
- boolean requiresShadeOpen) {
- action.onDismiss();
- }
- };
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+ private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil(
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter);
private RecordingService mRecordingService;
+ private class RecordingServiceTestable extends RecordingService {
+ RecordingServiceTestable(
+ RecordingController controller, Executor executor,
+ Handler handler, UiEventLogger uiEventLogger,
+ NotificationManager notificationManager,
+ UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
+ super(controller, executor, handler,
+ uiEventLogger, notificationManager, userContextTracker, keyguardDismissUtil);
+ attachBaseContext(mContext);
+ }
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
- mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+ mRecordingService = Mockito.spy(new RecordingServiceTestable(mController, mExecutor,
+ mHandler, mUiEventLogger, mNotificationManager,
+ mUserContextTracker, mKeyguardDismissUtil));
// Return actual context info
doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -160,8 +183,7 @@
Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
mRecordingService.onStartCommand(startIntent, 0, 0);
- // Then the state is set to not recording
- verify(mController).updateState(false);
+ assertUpdateState(false);
}
@Test
@@ -179,7 +201,7 @@
mRecordingService.onStopped();
- verify(mController).updateState(false);
+ assertUpdateState(false);
}
@Test
@@ -224,8 +246,21 @@
verify(mExecutor).execute(mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
- // Then the state is set to not recording and we cancel the notification
- verify(mController).updateState(false);
+ assertUpdateState(false);
verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
}
+
+ private void assertUpdateState(boolean state) {
+ // Then the state is set to not recording, and we cancel the notification
+ // non SYSTEM user doesn't have the reference to the correct controller,
+ // so a broadcast is sent in case of non SYSTEM user.
+ if (UserHandle.USER_SYSTEM == mContext.getUserId()) {
+ verify(mController).updateState(state);
+ } else {
+ ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mRecordingService).sendBroadcast(argumentCaptor.capture(), eq(PERMISSION_SELF));
+ assertEquals(RecordingController.INTENT_UPDATE_STATE,
+ argumentCaptor.getValue().getAction());
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 5b094c9..07feedf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -104,6 +104,26 @@
assertThat(visibility).isEqualTo(View.VISIBLE)
}
+ @Test
+ fun showDialog_dialogIsShowing() {
+ dialog.show()
+
+ assertThat(dialog.isShowing).isTrue()
+ }
+
+ @Test
+ fun showDialog_cancelClicked_dialogIsDismissed() {
+ dialog.show()
+
+ clickOnCancel()
+
+ assertThat(dialog.isShowing).isFalse()
+ }
+
+ private fun clickOnCancel() {
+ dialog.requireViewById<View>(android.R.id.button2).performClick()
+ }
+
private fun onSpinnerItemSelected(position: Int) {
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 34d09a9..ff047aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -580,6 +580,30 @@
verify(mQs).setQsVisible(true);
}
+ @Test
+ public void calculateBottomCornerRadius_scrimScaleMax() {
+ when(mScrimController.getBackScaling()).thenReturn(1.0f);
+ assertThat(mQsController.calculateBottomCornerRadius(0.0f)).isEqualTo(0);
+ }
+
+ @Test
+ public void calculateBottomCornerRadius_scrimScaleMin() {
+ when(mScrimController.getBackScaling())
+ .thenReturn(mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE);
+ assertThat(mQsController.calculateBottomCornerRadius(0.0f))
+ .isEqualTo(mQsController.getScrimCornerRadius());
+ }
+
+ @Test
+ public void calculateBottomCornerRadius_scrimScaleCutoff() {
+ float ratio = 1 / mQsController.calculateBottomRadiusProgress();
+ float cutoffScale = 1 - mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE / ratio;
+ when(mScrimController.getBackScaling())
+ .thenReturn(cutoffScale);
+ assertThat(mQsController.calculateBottomCornerRadius(0.0f))
+ .isEqualTo(mQsController.getScrimCornerRadius());
+ }
+
private void lockScreen() {
mQsController.setBarState(KEYGUARD);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index fd9f6a7..4ed113f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -287,7 +287,6 @@
@Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
@Mock private ScreenPinningRequest mScreenPinningRequest;
@Mock private PluginDependencyProvider mPluginDependencyProvider;
- @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
@@ -516,7 +515,6 @@
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
mPluginDependencyProvider,
- mKeyguardDismissUtil,
mExtensionController,
mUserInfoControllerImpl,
mPhoneStatusBarPolicy,
@@ -1103,8 +1101,10 @@
clearInvocations(mNotificationPanelViewController);
mCentralSurfaces.mWakefulnessObserver.onStartedWakingUp();
- verify(mDozeServiceHost).stopDozing();
+ verify(mDozeServiceHost, never()).stopDozing();
verify(mNotificationPanelViewController).expand(eq(false));
+ mCentralSurfaces.mWakefulnessObserver.onFinishedWakingUp();
+ verify(mDozeServiceHost).stopDozing();
}
@Test
@@ -1118,6 +1118,8 @@
mCentralSurfaces.setBouncerShowing(true);
mCentralSurfaces.mWakefulnessObserver.onStartedWakingUp();
verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+ mCentralSurfaces.mWakefulnessObserver.onFinishedWakingUp();
+ verify(mDozeServiceHost).stopDozing();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
new file mode 100644
index 0000000..b0aa2d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.phone;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardDismissUtilTest extends SysuiTestCase {
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private ActivityStarter.OnDismissAction mAction;
+
+ private KeyguardDismissUtil mKeyguardDismissUtil;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mKeyguardDismissUtil = new KeyguardDismissUtil(
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter);
+ }
+
+ @Test
+ public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsShowing() {
+ doReturn(true).when(mKeyguardStateController).isShowing();
+
+ mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+ true /* afterKeyguardGone */);
+
+ verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true);
+
+ verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+ }
+
+ @Test
+ public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsNotShowing() {
+ doReturn(false).when(mKeyguardStateController).isShowing();
+
+ mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+ true /* afterKeyguardGone */);
+
+ //no interaction with mStatusBarStateController
+ verify(mStatusBarStateController, times(0)).setLeaveOpenOnKeyguardHide(true);
+
+ verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e56f0d6..3eea93c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -352,6 +352,17 @@
}
@Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenGoingAway() {
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
+ }
+
+ @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
// Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
// the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 3b0d512..ae38958e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,9 +51,11 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -107,14 +109,15 @@
private SmartReplyInflaterImpl mSmartReplyInflater;
private SmartActionInflaterImpl mSmartActionInflater;
+ private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock private SmartReplyConstants mConstants;
@Mock private ActivityStarter mActivityStarter;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
@Mock private SmartReplyController mSmartReplyController;
-
- private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Before
public void setUp() {
@@ -122,12 +125,15 @@
mReceiver = new BlockingQueueIntentReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION),
Context.RECEIVER_EXPORTED_UNAUDITED);
- mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss());
+
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationRemoteInputManager.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
+ mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
+ mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
+
// Any number of replies are fine.
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
@@ -153,6 +159,13 @@
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
+ mKeyguardDismissUtil = new KeyguardDismissUtil(
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+ public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+ boolean requiresShadeOpen, boolean afterKeyguardGone) {
+ action.onDismiss();
+ }
+ };
mSmartReplyInflater = new SmartReplyInflaterImpl(
mConstants,
mKeyguardDismissUtil,
@@ -185,7 +198,17 @@
@Test
public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
- mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> { });
+ mKeyguardDismissUtil = new KeyguardDismissUtil(
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+ public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+ boolean requiresShadeOpen, boolean afterKeyguardGone) { }};
+ mSmartReplyInflater = new SmartReplyInflaterImpl(
+ mConstants,
+ mKeyguardDismissUtil,
+ mNotificationRemoteInputManager,
+ mSmartReplyController,
+ mContext);
+
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
@@ -196,9 +219,20 @@
@Test
public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
+ mKeyguardDismissUtil = new KeyguardDismissUtil(
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+ public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+ boolean requiresShadeOpen, boolean afterKeyguardGone) {
+ actionRef.set(action);
+ }
+ };
+ mSmartReplyInflater = new SmartReplyInflaterImpl(
+ mConstants,
+ mKeyguardDismissUtil,
+ mNotificationRemoteInputManager,
+ mSmartReplyController,
+ mContext);
- mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone)
- -> actionRef.set(action));
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index fc758cb..1a57bc1 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -761,6 +761,18 @@
}
// Called by Shell command
+ boolean isFieldDetectionServiceEnabledForUser(@UserIdInt int userId) {
+ enforceCallingPermissionForManagement();
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ return service.isPccClassificationEnabled();
+ }
+ }
+ return false;
+ }
+
+ // Called by Shell command
String getFieldDetectionServiceName(@UserIdInt int userId) {
enforceCallingPermissionForManagement();
return mFieldClassificationResolver.readServiceName(userId);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index cd6de87..c66fb81 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -26,7 +26,6 @@
import android.os.ShellCommand;
import android.os.UserHandle;
import android.service.autofill.AutofillFieldClassificationService.Scores;
-import android.text.TextUtils;
import android.view.autofill.AutofillManager;
import com.android.internal.os.IResultReceiver;
@@ -348,9 +347,7 @@
private int isFieldDetectionServiceEnabled(PrintWriter pw) {
final int userId = getNextIntArgRequired();
- String name = mService.getFieldDetectionServiceName(userId);
- boolean pccFlagEnabled = mService.isPccClassificationFlagEnabled();
- boolean enabled = (!TextUtils.isEmpty(name)) && pccFlagEnabled;
+ boolean enabled = mService.isFieldDetectionServiceEnabledForUser(userId);
pw.println(enabled);
return 0;
}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
new file mode 100644
index 0000000..a3efb25
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionRequestConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.autofill;
+
+import android.util.Slog;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+
+class InlineSuggestionRequestConsumer implements Consumer<InlineSuggestionsRequest> {
+
+ static final String TAG = "InlineSuggestionRequestConsumer";
+
+ private final WeakReference<Session.AssistDataReceiverImpl> mAssistDataReceiverWeakReference;
+ private final WeakReference<ViewState> mViewStateWeakReference;
+
+ InlineSuggestionRequestConsumer(WeakReference<Session.AssistDataReceiverImpl>
+ assistDataReceiverWeakReference,
+ WeakReference<ViewState> viewStateWeakReference) {
+ mAssistDataReceiverWeakReference = assistDataReceiverWeakReference;
+ mViewStateWeakReference = viewStateWeakReference;
+ }
+
+ @Override
+ public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
+ Session.AssistDataReceiverImpl assistDataReceiver = mAssistDataReceiverWeakReference.get();
+ ViewState viewState = mViewStateWeakReference.get();
+ if (assistDataReceiver == null) {
+ Slog.wtf(TAG, "assistDataReceiver is null when accepting new inline suggestion"
+ + "requests");
+ return;
+ }
+
+ if (viewState == null) {
+ Slog.wtf(TAG, "view state is null when accepting new inline suggestion requests");
+ return;
+ }
+ assistDataReceiver.handleInlineSuggestionRequest(inlineSuggestionsRequest, viewState);
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 44c5033..4576abb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -323,7 +323,7 @@
* Id of the View currently being displayed.
*/
@GuardedBy("mLock")
- @Nullable AutofillId mCurrentViewId;
+ private @Nullable AutofillId mCurrentViewId;
@GuardedBy("mLock")
private IAutoFillManagerClient mClient;
@@ -614,7 +614,7 @@
* TODO(b/151867668): improve how asynchronous data dependencies are handled, without using
* CountDownLatch.
*/
- private final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
+ final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
@GuardedBy("mLock")
private boolean mWaitForInlineRequest;
@GuardedBy("mLock")
@@ -629,17 +629,28 @@
mPendingFillRequest = null;
mWaitForInlineRequest = isInlineRequest;
mPendingInlineSuggestionsRequest = null;
- return isInlineRequest ? (inlineSuggestionsRequest) -> {
- synchronized (mLock) {
- if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
- return;
- }
- mWaitForInlineRequest = inlineSuggestionsRequest != null;
- mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
- maybeRequestFillLocked();
- viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+ if (isInlineRequest) {
+ WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference =
+ new WeakReference<AssistDataReceiverImpl>(this);
+ WeakReference<ViewState> viewStateWeakReference =
+ new WeakReference<ViewState>(viewState);
+ return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference,
+ viewStateWeakReference);
+ }
+ return null;
+ }
+
+ void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
+ ViewState viewState) {
+ synchronized (mLock) {
+ if (!mWaitForInlineRequest || mPendingInlineSuggestionsRequest != null) {
+ return;
}
- } : null;
+ mWaitForInlineRequest = inlineSuggestionsRequest != null;
+ mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
+ maybeRequestFillLocked();
+ viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
+ }
}
@GuardedBy("mLock")
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 61fc32d..51359ad 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -163,13 +163,35 @@
private boolean mDisabledByDeviceConfig;
// Device-config settings that are cached and passed back to apps
- @GuardedBy("mLock") int mDevCfgLoggingLevel;
- @GuardedBy("mLock") int mDevCfgMaxBufferSize;
- @GuardedBy("mLock") int mDevCfgIdleFlushingFrequencyMs;
- @GuardedBy("mLock") int mDevCfgTextChangeFlushingFrequencyMs;
- @GuardedBy("mLock") int mDevCfgLogHistorySize;
- @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
- @GuardedBy("mLock") boolean mDevCfgDisableFlushForViewTreeAppearing;
+ @GuardedBy("mLock")
+ int mDevCfgLoggingLevel;
+
+ @GuardedBy("mLock")
+ int mDevCfgMaxBufferSize;
+
+ @GuardedBy("mLock")
+ int mDevCfgIdleFlushingFrequencyMs;
+
+ @GuardedBy("mLock")
+ int mDevCfgTextChangeFlushingFrequencyMs;
+
+ @GuardedBy("mLock")
+ int mDevCfgLogHistorySize;
+
+ @GuardedBy("mLock")
+ int mDevCfgIdleUnbindTimeoutMs;
+
+ @GuardedBy("mLock")
+ boolean mDevCfgDisableFlushForViewTreeAppearing;
+
+ @GuardedBy("mLock")
+ boolean mDevCfgEnableContentProtectionReceiver;
+
+ @GuardedBy("mLock")
+ int mDevCfgContentProtectionAppsBlocklistSize;
+
+ @GuardedBy("mLock")
+ int mDevCfgContentProtectionBufferSize;
private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -362,6 +384,11 @@
case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT:
case ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING:
+ case ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
+ case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
+ case ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
setFineTuneParamsFromDeviceConfig();
return;
default:
@@ -372,39 +399,78 @@
private void setFineTuneParamsFromDeviceConfig() {
synchronized (mLock) {
- mDevCfgMaxBufferSize = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
- ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
- mDevCfgIdleFlushingFrequencyMs = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
- ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
- mDevCfgTextChangeFlushingFrequencyMs = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
- ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
- mDevCfgLogHistorySize = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
- mDevCfgIdleUnbindTimeoutMs = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
- (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
- mDevCfgDisableFlushForViewTreeAppearing = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
- false);
+ mDevCfgMaxBufferSize =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
+ ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
+ mDevCfgIdleFlushingFrequencyMs =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
+ ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
+ mDevCfgTextChangeFlushingFrequencyMs =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
+ ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
+ mDevCfgLogHistorySize =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE,
+ 20);
+ mDevCfgIdleUnbindTimeoutMs =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
+ (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
+ mDevCfgDisableFlushForViewTreeAppearing =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+ false);
+ mDevCfgEnableContentProtectionReceiver =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+ mDevCfgContentProtectionAppsBlocklistSize =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
+ ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+ mDevCfgContentProtectionBufferSize =
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager
+ .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
+ ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
if (verbose) {
- Slog.v(TAG, "setFineTuneParamsFromDeviceConfig(): "
- + "bufferSize=" + mDevCfgMaxBufferSize
- + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
- + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
- + ", logHistory=" + mDevCfgLogHistorySize
- + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs
- + ", disableFlushForViewTreeAppearing="
- + mDevCfgDisableFlushForViewTreeAppearing);
+ Slog.v(
+ TAG,
+ "setFineTuneParamsFromDeviceConfig(): "
+ + "bufferSize="
+ + mDevCfgMaxBufferSize
+ + ", idleFlush="
+ + mDevCfgIdleFlushingFrequencyMs
+ + ", textFluxh="
+ + mDevCfgTextChangeFlushingFrequencyMs
+ + ", logHistory="
+ + mDevCfgLogHistorySize
+ + ", idleUnbindTimeoutMs="
+ + mDevCfgIdleUnbindTimeoutMs
+ + ", disableFlushForViewTreeAppearing="
+ + mDevCfgDisableFlushForViewTreeAppearing
+ + ", enableContentProtectionReceiver="
+ + mDevCfgEnableContentProtectionReceiver
+ + ", contentProtectionAppsBlocklistSize="
+ + mDevCfgContentProtectionAppsBlocklistSize
+ + ", contentProtectionBufferSize="
+ + mDevCfgContentProtectionBufferSize);
}
}
}
@@ -645,21 +711,46 @@
final String prefix2 = prefix + " ";
- pw.print(prefix); pw.print("Users disabled by Settings: "); pw.println(mDisabledBySettings);
- pw.print(prefix); pw.println("DeviceConfig Settings: ");
- pw.print(prefix2); pw.print("disabled: "); pw.println(mDisabledByDeviceConfig);
- pw.print(prefix2); pw.print("loggingLevel: "); pw.println(mDevCfgLoggingLevel);
- pw.print(prefix2); pw.print("maxBufferSize: "); pw.println(mDevCfgMaxBufferSize);
- pw.print(prefix2); pw.print("idleFlushingFrequencyMs: ");
+ pw.print(prefix);
+ pw.print("Users disabled by Settings: ");
+ pw.println(mDisabledBySettings);
+ pw.print(prefix);
+ pw.println("DeviceConfig Settings: ");
+ pw.print(prefix2);
+ pw.print("disabled: ");
+ pw.println(mDisabledByDeviceConfig);
+ pw.print(prefix2);
+ pw.print("loggingLevel: ");
+ pw.println(mDevCfgLoggingLevel);
+ pw.print(prefix2);
+ pw.print("maxBufferSize: ");
+ pw.println(mDevCfgMaxBufferSize);
+ pw.print(prefix2);
+ pw.print("idleFlushingFrequencyMs: ");
pw.println(mDevCfgIdleFlushingFrequencyMs);
- pw.print(prefix2); pw.print("textChangeFlushingFrequencyMs: ");
+ pw.print(prefix2);
+ pw.print("textChangeFlushingFrequencyMs: ");
pw.println(mDevCfgTextChangeFlushingFrequencyMs);
- pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
- pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
+ pw.print(prefix2);
+ pw.print("logHistorySize: ");
+ pw.println(mDevCfgLogHistorySize);
+ pw.print(prefix2);
+ pw.print("idleUnbindTimeoutMs: ");
pw.println(mDevCfgIdleUnbindTimeoutMs);
- pw.print(prefix2); pw.print("disableFlushForViewTreeAppearing: ");
+ pw.print(prefix2);
+ pw.print("disableFlushForViewTreeAppearing: ");
pw.println(mDevCfgDisableFlushForViewTreeAppearing);
- pw.print(prefix); pw.println("Global Options:");
+ pw.print(prefix2);
+ pw.print("enableContentProtectionReceiver: ");
+ pw.println(mDevCfgEnableContentProtectionReceiver);
+ pw.print(prefix2);
+ pw.print("contentProtectionAppsBlocklistSize: ");
+ pw.println(mDevCfgContentProtectionAppsBlocklistSize);
+ pw.print(prefix2);
+ pw.print("contentProtectionBufferSize: ");
+ pw.println(mDevCfgContentProtectionBufferSize);
+ pw.print(prefix);
+ pw.println("Global Options:");
mGlobalContentCaptureOptions.dump(prefix2, pw);
}
@@ -1019,11 +1110,19 @@
}
synchronized (mLock) {
- final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
- mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
- mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
- mDevCfgDisableFlushForViewTreeAppearing,
- whitelistedComponents);
+ final ContentCaptureOptions options =
+ new ContentCaptureOptions(
+ mDevCfgLoggingLevel,
+ mDevCfgMaxBufferSize,
+ mDevCfgIdleFlushingFrequencyMs,
+ mDevCfgTextChangeFlushingFrequencyMs,
+ mDevCfgLogHistorySize,
+ mDevCfgDisableFlushForViewTreeAppearing,
+ /* enableReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ mDevCfgEnableContentProtectionReceiver,
+ mDevCfgContentProtectionBufferSize),
+ whitelistedComponents);
if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
return options;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 31d60f2..00dbb97 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3797,7 +3797,9 @@
}
clientPsr.addConnection(c);
c.startAssociationIfNeeded();
- if (c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+ // Don't set hasAboveClient if binding to self to prevent modifyRawOomAdj() from
+ // dropping the process' adjustment level.
+ if (b.client != s.app && c.hasFlag(Context.BIND_ABOVE_CLIENT)) {
clientPsr.setHasAboveClient(true);
}
if (c.hasFlag(BIND_ALLOW_WHITELIST_MANAGEMENT)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3d02c96..460ce44 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3393,7 +3393,7 @@
mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
}
- ProcessList.killProcessGroup(app.uid, pid);
+ app.killProcessGroupIfNecessaryLocked(true);
synchronized (mProcLock) {
app.setKilled(true);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 85a0185..6c3f01e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3253,7 +3253,11 @@
// {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
app.getWindowProcessController().onTopProcChanged();
- setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ if (mService.mUseFifoUiScheduling) {
+ mService.scheduleAsFifoPriority(app.getPid(), true);
+ } else {
+ setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ }
initialSchedGroup = SCHED_GROUP_TOP_APP;
} catch (Exception e) {
Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 335d676..d6495c7 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1197,26 +1197,7 @@
EventLog.writeEvent(EventLogTags.AM_KILL,
userId, mPid, processName, mState.getSetAdj(), reason);
Process.killProcessQuiet(mPid);
- final boolean killProcessGroup;
- if (mHostingRecord != null
- && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) {
- synchronized (ProcessRecord.this) {
- killProcessGroup = mProcessGroupCreated;
- if (!killProcessGroup) {
- // The process group hasn't been created, request to skip it.
- mSkipProcessGroupCreation = true;
- }
- }
- } else {
- killProcessGroup = true;
- }
- if (killProcessGroup) {
- if (asyncKPG) {
- ProcessList.killProcessGroup(uid, mPid);
- } else {
- Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
- }
- }
+ killProcessGroupIfNecessaryLocked(asyncKPG);
} else {
mPendingStart = false;
}
@@ -1231,6 +1212,30 @@
}
}
+ @GuardedBy("mService")
+ void killProcessGroupIfNecessaryLocked(boolean async) {
+ final boolean killProcessGroup;
+ if (mHostingRecord != null
+ && (mHostingRecord.usesWebviewZygote() || mHostingRecord.usesAppZygote())) {
+ synchronized (ProcessRecord.this) {
+ killProcessGroup = mProcessGroupCreated;
+ if (!killProcessGroup) {
+ // The process group hasn't been created, request to skip it.
+ mSkipProcessGroupCreation = true;
+ }
+ }
+ } else {
+ killProcessGroup = true;
+ }
+ if (killProcessGroup) {
+ if (async) {
+ ProcessList.killProcessGroup(uid, mPid);
+ } else {
+ Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
+ }
+ }
+ }
+
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
dumpDebug(proto, fieldId, -1);
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 81d0b6a..7ff6d11 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -341,7 +341,8 @@
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+ if (cr.binding.service.app.mServices != this
+ && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 21ade1b..fc3d7c8 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -156,7 +156,7 @@
@Override
public OperationContextExt updateContext(@NonNull OperationContextExt operationContext,
boolean isCryptoOperation) {
- return operationContext.update(this);
+ return operationContext.update(this, isCryptoOperation);
}
@Nullable
@@ -238,7 +238,7 @@
private void notifySubscribers() {
mSubscribers.forEach((context, consumer) -> {
- consumer.accept(context.update(this).toAidlContext());
+ consumer.accept(context.update(this, context.isCrypto()).toAidlContext());
});
}
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index 4d821e9..4a10e8e 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -241,9 +241,10 @@
}
/** Update this object with the latest values from the given context. */
- OperationContextExt update(@NonNull BiometricContext biometricContext) {
+ OperationContextExt update(@NonNull BiometricContext biometricContext, boolean isCrypto) {
mAidlContext.isAod = biometricContext.isAod();
mAidlContext.displayState = toAidlDisplayState(biometricContext.getDisplayState());
+ mAidlContext.isCrypto = isCrypto;
setFirstSessionId(biometricContext);
mIsDisplayOn = biometricContext.isDisplayOn();
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index b474cad..aa5f9fa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -32,6 +32,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.biometrics.BiometricSchedulerProto;
import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -569,9 +570,10 @@
if (mCurrentOperation == null) {
return;
}
- final BiometricSchedulerOperation mOperation = mCurrentOperation;
+ final BiometricSchedulerOperation operation = mCurrentOperation;
mHandler.postDelayed(() -> {
- if (mOperation == mCurrentOperation) {
+ if (operation == mCurrentOperation) {
+ Counter.logIncrement("biometric.scheduler_watchdog_triggered_count");
clearScheduler();
}
}, 10000);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index c2994a9..6f26e7b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -415,6 +415,11 @@
}
}
}
+
+ @Override
+ public void onError(int error, int vendorCode) throws RemoteException {
+ receiver.onError(error, vendorCode);
+ }
};
// This effectively iterates through all sensors, but has to do so by finding all
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 2e8e7c2..d647757 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -335,11 +335,8 @@
// Initialize to active (normal) screen brightness mode
switchToInteractiveScreenBrightnessMode();
- if (userLux != BrightnessMappingStrategy.NO_USER_LUX
- && userBrightness != BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
- // Use the given short-term model
- setScreenBrightnessByUser(userLux, userBrightness);
- }
+ // Use the given short-term model
+ setScreenBrightnessByUser(userLux, userBrightness);
}
/**
@@ -522,6 +519,10 @@
}
private boolean setScreenBrightnessByUser(float lux, float brightness) {
+ if (lux == BrightnessMappingStrategy.NO_USER_LUX
+ || brightness == BrightnessMappingStrategy.NO_USER_BRIGHTNESS) {
+ return false;
+ }
mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
mShortTermModel.setUserBrightness(lux, brightness);
return true;
@@ -1237,14 +1238,14 @@
// light.
// The anchor determines what were the light levels when the user has set their preference,
// and we use a relative threshold to determine when to revert to the OEM curve.
- private float mAnchor = -1f;
- private float mBrightness;
- private boolean mIsValid = true;
+ private float mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
+ private float mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ private boolean mIsValid = false;
private void reset() {
- mAnchor = -1f;
- mBrightness = -1f;
- mIsValid = true;
+ mAnchor = BrightnessMappingStrategy.NO_USER_LUX;
+ mBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ mIsValid = false;
}
private void invalidate() {
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index de42370..651828b 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -40,6 +40,7 @@
private final LogicalDisplay mLogicalDisplay;
+ private int mUserSerial;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
@@ -56,13 +57,15 @@
@GuardedBy("mSyncRoot")
private float mBrightness;
- BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+ BrightnessSetting(int userSerial,
+ @NonNull PersistentDataStore persistentDataStore,
@NonNull LogicalDisplay logicalDisplay,
DisplayManagerService.SyncRoot syncRoot) {
mPersistentDataStore = persistentDataStore;
mLogicalDisplay = logicalDisplay;
+ mUserSerial = userSerial;
mBrightness = mPersistentDataStore.getBrightness(
- mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked(), userSerial);
mSyncRoot = syncRoot;
}
@@ -96,8 +99,13 @@
mListeners.remove(l);
}
+ /** Sets the user serial for the brightness setting */
+ public void setUserSerial(int userSerial) {
+ mUserSerial = userSerial;
+ }
+
/**
- * Sets the brigtness and broadcasts the change to the listeners.
+ * Sets the brightness and broadcasts the change to the listeners.
* @param brightness The value to which the brightness is to be set.
*/
public void setBrightness(float brightness) {
@@ -112,7 +120,8 @@
// changed.
if (brightness != mBrightness) {
mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
- brightness);
+ brightness, mUserSerial
+ );
}
mBrightness = brightness;
int toSend = Float.floatToIntBits(mBrightness);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3844529..be65c53 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -652,6 +652,12 @@
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
+ // change the brightness value according to the selected user.
+ final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+ if (device != null) {
+ dpc.setBrightness(
+ mPersistentDataStore.getBrightness(device, userSerial), userSerial);
+ }
}
dpc.onSwitchUser(newUserId);
});
@@ -3134,8 +3140,9 @@
mBrightnessTracker = new BrightnessTracker(mContext, null);
}
- final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
- display, mSyncRoot);
+ final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
+ final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
+ mPersistentDataStore, display, mSyncRoot);
final DisplayPowerControllerInterface displayPowerController;
// If display is internal and has a HighBrightnessModeMetadata mapping, use that.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2b5523a..7d8bde9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2664,9 +2664,10 @@
}
@Override
- public void setBrightness(float brightnessValue) {
+ public void setBrightness(float brightnessValue, int userSerial) {
// Update the setting, which will eventually call back into DPC to have us actually update
// the display with the new value.
+ mBrightnessSetting.setUserSerial(userSerial);
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index cc9f539..040cecc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2172,8 +2172,8 @@
}
@Override
- public void setBrightness(float brightnessValue) {
- mDisplayBrightnessController.setBrightness(brightnessValue);
+ public void setBrightness(float brightnessValue, int userSerial) {
+ mDisplayBrightnessController.setBrightness(brightnessValue, userSerial);
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 73edb97..5fbbcbd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -29,7 +29,7 @@
* An interface to manage the display's power state and brightness
*/
public interface DisplayPowerControllerInterface {
-
+ int DEFAULT_USER_SERIAL = -1;
/**
* Notified when the display is changed.
*
@@ -98,7 +98,17 @@
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
*/
- void setBrightness(float brightness);
+ default void setBrightness(float brightness) {
+ setBrightness(brightness, DEFAULT_USER_SERIAL);
+ }
+
+ /**
+ * Set the screen brightness of the associated display
+ * @param brightness The value to which the brightness is to be set
+ * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
+ * if brightness needs to be updated for the current user.
+ */
+ void setBrightness(float brightness, int userSerial);
/**
* Checks if the proximity sensor is available
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 6d6ed72..2d7792d 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -133,6 +133,7 @@
private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
"brightness-nits-for-default-display";
+ public static final int DEFAULT_USER_ID = -1;
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -294,7 +295,7 @@
return false;
}
- public float getBrightness(DisplayDevice device) {
+ public float getBrightness(DisplayDevice device, int userSerial) {
if (device == null || !device.hasStableUniqueId()) {
return Float.NaN;
}
@@ -302,10 +303,10 @@
if (state == null) {
return Float.NaN;
}
- return state.getBrightness();
+ return state.getBrightness(userSerial);
}
- public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+ public boolean setBrightness(DisplayDevice displayDevice, float brightness, int userSerial) {
if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
return false;
}
@@ -314,7 +315,7 @@
return false;
}
final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
- if (state.setBrightness(brightness)) {
+ if (state.setBrightness(brightness, userSerial)) {
setDirty();
return true;
}
@@ -611,6 +612,7 @@
state.saveToXml(serializer);
serializer.endTag(null, TAG_DISPLAY);
}
+
serializer.endTag(null, TAG_DISPLAY_STATES);
serializer.startTag(null, TAG_STABLE_DEVICE_VALUES);
mStableDeviceValues.saveToXml(serializer);
@@ -649,7 +651,8 @@
private static final class DisplayState {
private int mColorMode;
- private float mBrightness = Float.NaN;
+
+ private SparseArray<Float> mPerUserBrightness = new SparseArray<>();
private int mWidth;
private int mHeight;
private float mRefreshRate;
@@ -670,16 +673,25 @@
return mColorMode;
}
- public boolean setBrightness(float brightness) {
- if (brightness == mBrightness) {
+ public boolean setBrightness(float brightness, int userSerial) {
+ // Remove the default user brightness, before setting a new user-specific value.
+ // This is a one-time operation, required to restructure the config after user-specific
+ // brightness was introduced.
+ mPerUserBrightness.remove(DEFAULT_USER_ID);
+
+ if (getBrightness(userSerial) == brightness) {
return false;
}
- mBrightness = brightness;
+ mPerUserBrightness.set(userSerial, brightness);
return true;
}
- public float getBrightness() {
- return mBrightness;
+ public float getBrightness(int userSerial) {
+ float brightness = mPerUserBrightness.get(userSerial, Float.NaN);
+ if (Float.isNaN(brightness)) {
+ brightness = mPerUserBrightness.get(DEFAULT_USER_ID, Float.NaN);
+ }
+ return brightness;
}
public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -729,12 +741,7 @@
mColorMode = Integer.parseInt(value);
break;
case TAG_BRIGHTNESS_VALUE:
- String brightness = parser.nextText();
- try {
- mBrightness = Float.parseFloat(brightness);
- } catch (NumberFormatException e) {
- mBrightness = Float.NaN;
- }
+ loadBrightnessFromXml(parser);
break;
case TAG_BRIGHTNESS_CONFIGURATIONS:
mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -760,11 +767,12 @@
serializer.text(Integer.toString(mColorMode));
serializer.endTag(null, TAG_COLOR_MODE);
- serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
- if (!Float.isNaN(mBrightness)) {
- serializer.text(Float.toString(mBrightness));
+ for (int i = 0; i < mPerUserBrightness.size(); i++) {
+ serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+ serializer.attributeInt(null, ATTR_USER_SERIAL, mPerUserBrightness.keyAt(i));
+ serializer.text(Float.toString(mPerUserBrightness.valueAt(i)));
+ serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
}
- serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
mDisplayBrightnessConfigurations.saveToXml(serializer);
@@ -785,12 +793,33 @@
public void dump(final PrintWriter pw, final String prefix) {
pw.println(prefix + "ColorMode=" + mColorMode);
- pw.println(prefix + "BrightnessValue=" + mBrightness);
+ pw.println(prefix + "BrightnessValues: ");
+ for (int i = 0; i < mPerUserBrightness.size(); i++) {
+ pw.println("User: " + mPerUserBrightness.keyAt(i)
+ + " Value: " + mPerUserBrightness.valueAt(i));
+ }
pw.println(prefix + "DisplayBrightnessConfigurations: ");
mDisplayBrightnessConfigurations.dump(pw, prefix);
pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
pw.println(prefix + "RefreshRate=" + mRefreshRate);
}
+
+ private void loadBrightnessFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ int userSerial;
+ try {
+ userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL);
+ } catch (NumberFormatException | XmlPullParserException e) {
+ userSerial = DEFAULT_USER_ID;
+ Slog.e(TAG, "Failed to read user serial", e);
+ }
+ String brightness = parser.nextText();
+ try {
+ mPerUserBrightness.set(userSerial, Float.parseFloat(brightness));
+ } catch (NumberFormatException nfe) {
+ Slog.e(TAG, "Failed to read brightness", nfe);
+ }
+ }
}
private static final class StableDeviceValues {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 7574de8..2f52b70 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -38,6 +38,8 @@
* display. Applies the chosen brightness.
*/
public final class DisplayBrightnessController {
+ private static final int DEFAULT_USER_SERIAL = -1;
+
// The ID of the display tied to this DisplayBrightnessController
private final int mDisplayId;
@@ -274,8 +276,16 @@
* Notifies the brightnessSetting to persist the supplied brightness value.
*/
public void setBrightness(float brightnessValue) {
+ setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
+ }
+
+ /**
+ * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+ */
+ public void setBrightness(float brightnessValue, int userSerial) {
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
+ mBrightnessSetting.setUserSerial(userSerial);
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 9f3ab88..c7b80af 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -493,8 +493,6 @@
// Add ourselves to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
-
- mSettingsObserver.registerAndUpdate();
}
// TODO(BT) Pass in parameter for bluetooth system
@@ -505,6 +503,8 @@
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mSettingsObserver.registerAndUpdate();
+
synchronized (mLidSwitchLock) {
mSystemReady = true;
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 651063e..42591f4 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -115,35 +115,34 @@
return setting != 0;
}
- private int getPointerSpeedValue(String settingName) {
- int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
- settingName, InputSettings.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+ private int constrainPointerSpeedValue(int speed) {
return Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
InputSettings.MAX_POINTER_SPEED);
}
private void updateMousePointerSpeed() {
- mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+ int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.POINTER_SPEED, InputSettings.DEFAULT_POINTER_SPEED,
+ UserHandle.USER_CURRENT);
+ mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
}
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
- getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+ constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
}
private void updateTouchpadNaturalScrollingEnabled() {
mNative.setTouchpadNaturalScrollingEnabled(
- getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true));
+ InputSettings.useTouchpadNaturalScrolling(mContext));
}
private void updateTouchpadTapToClickEnabled() {
- mNative.setTouchpadTapToClickEnabled(
- getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true));
+ mNative.setTouchpadTapToClickEnabled(InputSettings.useTouchpadTapToClick(mContext));
}
private void updateTouchpadRightClickZoneEnabled() {
- mNative.setTouchpadRightClickZoneEnabled(
- getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false));
+ mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
}
private void updateShowTouches() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index c076c05..c59b733 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
abstract class MediaRoute2Provider {
final ComponentName mComponentName;
@@ -56,7 +57,9 @@
public abstract void requestCreateSession(long requestId, String packageName, String routeId,
@Nullable Bundle sessionHints);
public abstract void releaseSession(long requestId, String sessionId);
- public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference);
+
+ public abstract void updateDiscoveryPreference(
+ Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference);
public abstract void selectRoute(long requestId, String sessionId, String routeId);
public abstract void deselectRoute(long requestId, String sessionId, String routeId);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 72b8436..3cf0786 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -49,6 +49,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Maintains a connection to a particular {@link MediaRoute2ProviderService}.
@@ -61,6 +62,7 @@
private final Context mContext;
private final int mUserId;
private final Handler mHandler;
+ private final boolean mIsSelfScanOnlyProvider;
// Connection state
private boolean mRunning;
@@ -70,14 +72,19 @@
private boolean mIsManagerScanning;
private RouteDiscoveryPreference mLastDiscoveryPreference = null;
+ private boolean mLastDiscoveryPreferenceIncludesThisPackage = false;
@GuardedBy("mLock")
final List<RoutingSessionInfo> mReleasingSessions = new ArrayList<>();
- MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
+ MediaRoute2ProviderServiceProxy(
+ @NonNull Context context,
+ @NonNull ComponentName componentName,
+ boolean isSelfScanOnlyProvider,
int userId) {
super(componentName);
mContext = Objects.requireNonNull(context, "Context must not be null.");
+ mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
mUserId = userId;
mHandler = new Handler(Looper.myLooper());
}
@@ -107,8 +114,11 @@
}
@Override
- public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+ public void updateDiscoveryPreference(
+ Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
mLastDiscoveryPreference = discoveryPreference;
+ mLastDiscoveryPreferenceIncludesThisPackage =
+ activelyScanningPackages.contains(mComponentName.getPackageName());
if (mConnectionReady) {
mActiveConnection.updateDiscoveryPreference(discoveryPreference);
}
@@ -209,11 +219,15 @@
private boolean shouldBind() {
if (mRunning) {
- // Bind when there is a discovery preference or an active route session.
- return (mLastDiscoveryPreference != null
- && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty())
- || !getSessionInfos().isEmpty()
- || mIsManagerScanning;
+ boolean shouldBind =
+ mLastDiscoveryPreference != null
+ && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
+ if (mIsSelfScanOnlyProvider) {
+ shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage;
+ }
+ shouldBind |= mIsManagerScanning;
+ shouldBind |= !getSessionInfos().isEmpty();
+ return shouldBind;
}
return false;
}
@@ -301,7 +315,11 @@
if (mActiveConnection == connection) {
mConnectionReady = true;
if (mLastDiscoveryPreference != null) {
- updateDiscoveryPreference(mLastDiscoveryPreference);
+ updateDiscoveryPreference(
+ mLastDiscoveryPreferenceIncludesThisPackage
+ ? Set.of(mComponentName.getPackageName())
+ : Set.of(),
+ mLastDiscoveryPreference);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 46bccaf..bd252e7 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -16,6 +16,8 @@
package com.android.server.media;
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -34,6 +36,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
/**
* Watches changes of packages, or scan them for finding media route providers.
@@ -41,8 +44,8 @@
final class MediaRoute2ProviderWatcher {
private static final String TAG = "MR2ProviderWatcher";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS_NONE =
- PackageManager.ResolveInfoFlags.of(0);
+ private static final PackageManager.ResolveInfoFlags RESOLVE_INFO_FLAGS =
+ PackageManager.ResolveInfoFlags.of(GET_RESOLVED_FILTER);
private final Context mContext;
private final Callback mCallback;
@@ -118,16 +121,26 @@
int targetIndex = 0;
Intent intent = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
for (ResolveInfo resolveInfo :
- mPackageManager.queryIntentServicesAsUser(
- intent, RESOLVE_INFO_FLAGS_NONE, mUserId)) {
+ mPackageManager.queryIntentServicesAsUser(intent, RESOLVE_INFO_FLAGS, mUserId)) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo != null) {
+ boolean isSelfScanOnlyProvider = false;
+ Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator();
+ if (categoriesIterator != null) {
+ while (categoriesIterator.hasNext()) {
+ isSelfScanOnlyProvider |=
+ MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(
+ categoriesIterator.next());
+ }
+ }
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
MediaRoute2ProviderServiceProxy proxy =
- new MediaRoute2ProviderServiceProxy(mContext,
- new ComponentName(serviceInfo.packageName, serviceInfo.name),
- mUserId);
+ new MediaRoute2ProviderServiceProxy(
+ mContext,
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ isSelfScanOnlyProvider,
+ mUserId);
proxy.start();
mProxies.add(targetIndex++, proxy);
mCallback.onAddProviderService(proxy);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b79991e..1059c19 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1478,6 +1478,7 @@
final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
+ Set<String> mActivelyScanningPackages = Set.of();
final UserHandler mHandler;
UserRecord(int userId) {
@@ -1525,7 +1526,12 @@
pw.println(indent + "<no manager records>");
}
- mCompositeDiscoveryPreference.dump(pw, indent);
+ pw.println(indent + "Composite discovery preference:");
+ mCompositeDiscoveryPreference.dump(pw, indent + " ");
+ pw.println(
+ indent
+ + "Packages actively scanning: "
+ + String.join(", ", mActivelyScanningPackages));
if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
pw.println(indent + "<could not dump handler state>");
@@ -1834,7 +1840,9 @@
public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
proxy.setCallback(this);
mRouteProviders.add(proxy);
- proxy.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
+ proxy.updateDiscoveryPreference(
+ mUserRecord.mActivelyScanningPackages,
+ mUserRecord.mCompositeDiscoveryPreference);
}
@Override
@@ -2341,8 +2349,8 @@
return;
}
notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
- notifySessionInfoChangedToRouters(getRouterRecords(false),
- mSystemProvider.getDefaultSessionInfo());
+ notifySessionInfoChangedToRouters(
+ getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
return;
}
@@ -2711,8 +2719,8 @@
if (service == null) {
return;
}
- List<RouteDiscoveryPreference> discoveryPreferences = Collections.emptyList();
- List<RouterRecord> routerRecords = getRouterRecords();
+ List<RouterRecord> activeRouterRecords = Collections.emptyList();
+ List<RouterRecord> allRouterRecords = getRouterRecords();
List<ManagerRecord> managerRecords = getManagerRecords();
boolean isManagerScanning = false;
@@ -2723,15 +2731,16 @@
<= sPackageImportanceForScanning);
if (isManagerScanning) {
- discoveryPreferences = routerRecords.stream()
- .map(record -> record.mDiscoveryPreference)
- .collect(Collectors.toList());
+ activeRouterRecords = allRouterRecords;
} else {
- discoveryPreferences = routerRecords.stream().filter(record ->
- service.mActivityManager.getPackageImportance(record.mPackageName)
- <= sPackageImportanceForScanning)
- .map(record -> record.mDiscoveryPreference)
- .collect(Collectors.toList());
+ activeRouterRecords =
+ allRouterRecords.stream()
+ .filter(
+ record ->
+ service.mActivityManager.getPackageImportance(
+ record.mPackageName)
+ <= sPackageImportanceForScanning)
+ .collect(Collectors.toList());
}
}
@@ -2748,22 +2757,30 @@
// to query route providers once to obtain all of the routes of interest, which
// can be subsequently filtered for the individual discovery preferences.
Set<String> preferredFeatures = new HashSet<>();
+ Set<String> activelyScanningPackages = new HashSet<>();
boolean activeScan = false;
- for (RouteDiscoveryPreference preference : discoveryPreferences) {
+ for (RouterRecord activeRouterRecord : activeRouterRecords) {
+ RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
preferredFeatures.addAll(preference.getPreferredFeatures());
- activeScan |= preference.shouldPerformActiveScan();
+ if (preference.shouldPerformActiveScan()) {
+ activeScan = true;
+ activelyScanningPackages.add(activeRouterRecord.mPackageName);
+ }
}
RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
List.copyOf(preferredFeatures), activeScan || isManagerScanning).build();
synchronized (service.mLock) {
- if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
+ if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
+ && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
return;
}
mUserRecord.mCompositeDiscoveryPreference = newPreference;
+ mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
}
for (MediaRoute2Provider provider : mRouteProviders) {
- provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
+ provider.updateDiscoveryPreference(
+ activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 426bc5e..f4e6abd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -42,6 +42,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -196,7 +197,8 @@
}
@Override
- public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+ public void updateDiscoveryPreference(
+ Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
// Do nothing
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d5009f6..b1a289f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1095,6 +1095,8 @@
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
+ + " press (isScreenOn=" + isScreenOn + ")");
noDreamAction.run();
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3db0315..2b2100e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5630,11 +5630,18 @@
setClientVisible(visible);
}
+ final DisplayContent displayContent = getDisplayContent();
if (!visible) {
mImeInsetsFrozenUntilStartInput = true;
+ if (usingShellTransitions) {
+ final WindowState wallpaperTarget =
+ displayContent.mWallpaperController.getWallpaperTarget();
+ if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
+ displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
+ }
+ }
}
- final DisplayContent displayContent = getDisplayContent();
if (!displayContent.mClosingApps.contains(this)
&& !displayContent.mOpeningApps.contains(this)
&& !fromTransition) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3e3eb57..53a4752 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1821,7 +1821,8 @@
// If Activity's launching into PiP, move the mStartActivity immediately to pinned mode.
// Note that mStartActivity and source should be in the same Task at this point.
if (mOptions != null && mOptions.isLaunchIntoPip()
- && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) {
+ && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()
+ && balCode != BAL_BLOCK) {
mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
sourceRecord, "launch-into-pip");
}
@@ -2925,15 +2926,6 @@
}
}
- // If the matching task is already in the adjacent task of the launch target. Adjust to use
- // the adjacent task as its launch target. So the existing task will be launched into the
- // closer one and won't be reparent redundantly.
- final Task adjacentTargetTask = mTargetRootTask.getAdjacentTask();
- if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)
- && intentTask.isOnTop()) {
- mTargetRootTask = adjacentTargetTask;
- }
-
// If the target task is not in the front, then we need to bring it to the front...
// except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
// the same behavior as if a new instance was being started, which means not bringing it
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4d4ffba..a650d77 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3560,10 +3560,10 @@
mRootWindowContainer.forAllDisplays(displayContent -> {
mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
});
- WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
- if (wallpaperManagerInternal != null) {
- wallpaperManagerInternal.onKeyguardGoingAway();
- }
+ }
+ WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+ if (wallpaperManagerInternal != null) {
+ wallpaperManagerInternal.onKeyguardGoingAway();
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e44d279..6aec969 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6511,6 +6511,22 @@
.getKeyguardController().isDisplayOccluded(mDisplayId);
}
+ /**
+ * @return the task that is occluding the keyguard
+ */
+ @Nullable
+ Task getTaskOccludingKeyguard() {
+ final KeyguardController keyguardController = mRootWindowContainer.mTaskSupervisor
+ .getKeyguardController();
+ if (keyguardController.getTopOccludingActivity(mDisplayId) != null) {
+ return keyguardController.getTopOccludingActivity(mDisplayId).getRootTask();
+ }
+ if (keyguardController.getDismissKeyguardActivity(mDisplayId) != null) {
+ return keyguardController.getDismissKeyguardActivity(mDisplayId).getRootTask();
+ }
+ return null;
+ }
+
@VisibleForTesting
void removeAllTasks() {
forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 812d2a5..8dcb042 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1859,6 +1859,9 @@
*/
final Rect mConfigFrame = new Rect();
+ /** The count of insets sources when calculating this info. */
+ int mLastInsetsSourceCount;
+
private boolean mNeedUpdate = true;
void update(DisplayContent dc, int rotation, int w, int h) {
@@ -1880,6 +1883,7 @@
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
+ mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size();
mNeedUpdate = false;
}
@@ -1888,6 +1892,7 @@
mConfigInsets.set(other.mConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
+ mLastInsetsSourceCount = other.mLastInsetsSourceCount;
mNeedUpdate = false;
}
@@ -1986,6 +1991,19 @@
newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
+ // Even if the config frame is not changed in current rotation, it may change the
+ // insets in other rotations if the source count is changed.
+ if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) {
+ for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) {
+ if (i != rotation) {
+ final boolean flipSize = (i + rotation) % 2 == 1;
+ final int w = flipSize ? dh : dw;
+ final int h = flipSize ? dw : dh;
+ mDecorInsets.mInfoForRotation[i].update(mDisplayContent, i, w, h);
+ }
+ }
+ mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+ }
return false;
}
if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5f6d660..99878a3 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -411,19 +410,20 @@
if (waitAppTransition) {
mService.deferWindowLayout();
try {
- mRootWindowContainer.getDefaultDisplay()
- .requestTransitionAndLegacyPrepare(
- isDisplayOccluded(DEFAULT_DISPLAY)
- ? TRANSIT_KEYGUARD_OCCLUDE
- : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+ if (isDisplayOccluded(DEFAULT_DISPLAY)) {
+ mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
+ TRANSIT_KEYGUARD_OCCLUDE,
+ topActivity != null ? topActivity.getRootTask() : null);
+ } else {
+ mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
+ TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+ }
updateKeyguardSleepToken(DEFAULT_DISPLAY);
mWindowManager.executeAppTransition();
} finally {
mService.continueWindowLayout();
}
}
- dismissMultiWindowModeForTaskIfNeeded(displayId, topActivity != null
- ? topActivity.getRootTask() : null);
}
/**
@@ -473,6 +473,14 @@
return getDisplayState(displayId).mOccluded;
}
+ ActivityRecord getTopOccludingActivity(int displayId) {
+ return getDisplayState(displayId).mTopOccludesActivity;
+ }
+
+ ActivityRecord getDismissKeyguardActivity(int displayId) {
+ return getDisplayState(displayId).mDismissingKeyguardActivity;
+ }
+
/**
* @return true if Keyguard can be currently dismissed without entering credentials.
*/
@@ -488,22 +496,6 @@
return getDisplayState(DEFAULT_DISPLAY).mShowingDream;
}
- private void dismissMultiWindowModeForTaskIfNeeded(int displayId,
- @Nullable Task currentTaskControllingOcclusion) {
- // TODO(b/113840485): Handle docked stack for individual display.
- if (!getDisplayState(displayId).mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
- return;
- }
-
- // Dismiss freeform windowing mode
- if (currentTaskControllingOcclusion == null) {
- return;
- }
- if (currentTaskControllingOcclusion.inFreeformWindowingMode()) {
- currentTaskControllingOcclusion.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- }
- }
-
private void updateKeyguardSleepToken() {
for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 5369159..7e83959 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2376,6 +2376,7 @@
if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled()
&& !display.mTransitionController.isCollecting()) {
int transit = TRANSIT_NONE;
+ Task startTask = null;
if (!display.getDisplayPolicy().isAwake()) {
// Note that currently this only happens on default display because non-default
// display is always awake.
@@ -2383,12 +2384,12 @@
} else if (display.isKeyguardOccluded()) {
// The display was awake so this is resuming activity for occluding keyguard.
transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+ startTask = display.getTaskOccludingKeyguard();
}
if (transit != TRANSIT_NONE) {
display.mTransitionController.requestStartTransition(
display.mTransitionController.createTransition(transit),
- null /* startTask */, null /* remoteTransition */,
- null /* displayChange */);
+ startTask, null /* remoteTransition */, null /* displayChange */);
}
}
// Set the sleeping state of the root tasks on the display.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d5a7ff5..b938b9e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2249,8 +2249,10 @@
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
+
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
+ rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
startT.setLayer(rootLeash, leashReference.getLastLayer());
outInfo.addRootLeash(endDisplayId, rootLeash,
ancestor.getBounds().left, ancestor.getBounds().top);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index edafe06..20ce98c 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -146,11 +146,10 @@
}
} else {
final ActivityRecord ar = w.mActivityRecord;
- final TransitionController tc = w.mTransitionController;
// The animating window can still be visible on screen if it is in transition, so we
// should check whether this window can be wallpaper target even when visibleRequested
// is false.
- if (ar != null && !ar.isVisibleRequested() && !tc.inTransition(ar)) {
+ if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
// An activity that is not going to remain visible shouldn't be the target.
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6020711..7464045 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11113,7 +11113,7 @@
|| hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS);
}
- private boolean canUserUseLockTaskLocked(int userId) {
+ private boolean canDPCManagedUserUseLockTaskLocked(int userId) {
if (isUserAffiliatedWithDeviceLocked(userId)) {
return true;
}
@@ -11122,19 +11122,16 @@
if (mOwners.hasDeviceOwner()) {
return false;
}
-
- if (!isPermissionCheckFlagEnabled() && !isPolicyEngineForFinanceFlagEnabled()) {
- final ComponentName profileOwner = getProfileOwnerAsUser(userId);
- if (profileOwner == null) {
- return false;
- }
+
+ final ComponentName profileOwner = getProfileOwnerAsUser(userId);
+ if (profileOwner == null) {
+ return false;
}
-
// Managed profiles are not allowed to use lock task
if (isManagedProfile(userId)) {
return false;
}
-
+
return true;
}
private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -11142,7 +11139,8 @@
final int userId = caller.getUserId();
enforceCanQuery(MANAGE_DEVICE_POLICY_LOCK_TASK, caller.getPackageName(), userId);
- if (!canUserUseLockTaskLocked(userId)) {
+ if ((isDeviceOwner(caller) || isProfileOwner(caller))
+ && !canDPCManagedUserUseLockTaskLocked(userId)) {
throw new SecurityException("User " + userId + " is not allowed to use lock task");
}
}
@@ -11158,7 +11156,8 @@
caller.getPackageName(),
userId
);
- if (!canUserUseLockTaskLocked(userId)) {
+ if ((isDeviceOwner(caller) || isProfileOwner(caller))
+ && !canDPCManagedUserUseLockTaskLocked(userId)) {
throw new SecurityException("User " + userId + " is not allowed to use lock task");
}
return enforcingAdmin;
@@ -11169,7 +11168,7 @@
|| isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
final int userId = caller.getUserId();
- if (!canUserUseLockTaskLocked(userId)) {
+ if (!canDPCManagedUserUseLockTaskLocked(userId)) {
throw new SecurityException("User " + userId + " is not allowed to use lock task");
}
}
@@ -15101,7 +15100,7 @@
final List<UserInfo> userInfos = mUserManager.getAliveUsers();
for (int i = userInfos.size() - 1; i >= 0; i--) {
int userId = userInfos.get(i).id;
- if (canUserUseLockTaskLocked(userId)) {
+ if (canDPCManagedUserUseLockTaskLocked(userId)) {
continue;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index cda5456..770f04a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -68,6 +68,7 @@
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.answer;
@@ -2489,6 +2490,28 @@
assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_AboveClient_SameProcess() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(app).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+ // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and
+ // verify that its OOM adjustment level is unaffected.
+ bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+ app.mServices.updateHasAboveClientLocked();
+ assertFalse(app.mServices.hasAboveClient());
+
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index fb3a5f6..a442303 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -316,13 +316,13 @@
assertThat(aidlContext.isAod).isEqualTo(false);
assertThat(aidlContext.isCrypto).isEqualTo(false);
- context = mProvider.updateContext(mOpContext, false /* crypto */);
+ context = mProvider.updateContext(mOpContext, true /* crypto */);
aidlContext = context.toAidlContext();
assertThat(context).isSameInstanceAs(mOpContext);
assertThat(aidlContext.id).isEqualTo(0);
assertThat(aidlContext.reason).isEqualTo(OperationReason.UNKNOWN);
assertThat(aidlContext.isAod).isEqualTo(false);
- assertThat(aidlContext.isCrypto).isEqualTo(false);
+ assertThat(aidlContext.isCrypto).isEqualTo(true);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
index 5cf5960..32284fd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/OperationContextExtTest.java
@@ -98,7 +98,7 @@
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
final OperationContextExt context = new OperationContextExt(newAidlContext(), true);
when(mBiometricContext.getDisplayState()).thenReturn(entry.getKey());
- assertThat(context.update(mBiometricContext).getDisplayState())
+ assertThat(context.update(mBiometricContext, context.isCrypto()).getDisplayState())
.isEqualTo(entry.getValue());
}
}
@@ -139,7 +139,7 @@
final OperationContextExt context = new OperationContextExt(newAidlContext(),
sessionType == OperationReason.BIOMETRIC_PROMPT);
- assertThat(context.update(mBiometricContext)).isSameInstanceAs(context);
+ assertThat(context.update(mBiometricContext, context.isCrypto())).isSameInstanceAs(context);
if (sessionInfo != null) {
assertThat(context.getId()).isEqualTo(sessionInfo.getId());
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
new file mode 100644
index 0000000..c872a11
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2013 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.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentCaptureManagerService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentcapture.ContentCaptureManagerServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SuppressWarnings("GuardedBy") // Service not really running, no need to expose locks
+public class ContentCaptureManagerServiceTest {
+
+ private static final int USER_ID = 1234;
+
+ private static final String PACKAGE_NAME = "com.test.package";
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private UserManagerInternal mMockUserManagerInternal;
+
+ private ContentCaptureManagerService mContentCaptureManagerService;
+
+ @Before
+ public void setup() {
+ when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+ mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+ }
+
+ @Test
+ public void getOptions_notAllowlisted() {
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNull();
+ }
+
+ @Test
+ public void getOptions_allowlisted_contentCaptureReceiver() {
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+ assertThat(actual.whitelistedComponents).isNull();
+ }
+
+ @Test
+ public void getOptions_allowlisted_bothReceivers() {
+ mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+ assertThat(actual.whitelistedComponents).isNull();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS
new file mode 100644
index 0000000..b3583a7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 17fa33c..a6acd60 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -476,6 +476,42 @@
}
@Test
+ public void testSwitchBetweenModesNoUserInteractions() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Sensor reads 123 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
+ when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
+ when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+ when(mBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+ // No user brightness interaction.
+
+ mController.switchToIdleMode();
+ when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true);
+ when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1.0f);
+ when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1.0f);
+
+ // Sensor reads 1000 lux,
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
+ // Do not fast-forward time.
+ mTestLooper.dispatchAll();
+
+ mController.switchToInteractiveScreenBrightnessMode();
+ // Do not fast-forward time
+ mTestLooper.dispatchAll();
+
+ // Ensure that there are no data points added, since the user has never adjusted the
+ // brightness
+ verify(mBrightnessMappingStrategy, times(0))
+ .addUserDataPoint(anyFloat(), anyFloat());
+ }
+
+ @Test
public void testSwitchToIdleMappingStrategy() throws Exception {
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 817b245..642f54c 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -60,6 +60,114 @@
}
@Test
+ public void testLoadBrightness() {
+ final String uniqueDisplayId = "test:123";
+ final DisplayDevice testDisplayDevice = new DisplayDevice(
+ null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<display-manager-state>\n"
+ + " <display-states>\n"
+ + " <display unique-id=\"test:123\">\n"
+ + " <brightness-value user-serial=\"1\">0.1</brightness-value>\n"
+ + " <brightness-value user-serial=\"2\">0.2</brightness-value>\n"
+ + " </display>\n"
+ + " </display-states>\n"
+ + "</display-manager-state>\n";
+
+ InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+ mInjector.setReadStream(is);
+ mDataStore.loadIfNeeded();
+
+ float brightness = mDataStore.getBrightness(testDisplayDevice, 1);
+ assertEquals(0.1, brightness, 0.01);
+
+ brightness = mDataStore.getBrightness(testDisplayDevice, 2);
+ assertEquals(0.2, brightness, 0.01);
+ }
+
+ @Test
+ public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
+ final String uniqueDisplayId = "test:123";
+ final DisplayDevice testDisplayDevice =
+ new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<display-manager-state>\n"
+ + " <display-states>\n"
+ + " <color-mode>0</color-mode>\n"
+ + " <display unique-id=\"test:123\">\n"
+ + " <brightness-value>0.5</brightness-value>\n"
+ + " </display>\n"
+ + " </display-states>\n"
+ + "</display-manager-state>\n";
+
+ InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+ mInjector.setReadStream(is);
+ mDataStore.loadIfNeeded();
+
+ float user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+ float user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+ assertEquals(0.5, user1Brightness, 0.01);
+ assertEquals(0.5, user2Brightness, 0.01);
+
+ // Override the value for user 2. Default user must have been removed.
+ mDataStore.setBrightness(testDisplayDevice, 0.2f, 2 /* userSerial */ /* brightness*/);
+
+ user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+ user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+ assertTrue(Float.isNaN(user1Brightness));
+ assertEquals(0.2f, user2Brightness, 0.01);
+
+ // Override the value for user 1. User-specific brightness values should co-exist.
+ mDataStore.setBrightness(testDisplayDevice, 0.1f, 1 /* userSerial */ /* brightness*/);
+ user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+ user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+ assertEquals(0.1f, user1Brightness, 0.01);
+ assertEquals(0.2f, user2Brightness, 0.01);
+
+ // Validate saveIfNeeded writes user-specific brightnes.
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+
+ user1Brightness = newDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+ user2Brightness = newDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+ float unknownUserBrightness =
+ newDataStore.getBrightness(testDisplayDevice, 999 /* userSerial */);
+ assertEquals(0.1f, user1Brightness, 0.01);
+ assertEquals(0.2f, user2Brightness, 0.01);
+ assertTrue(Float.isNaN(unknownUserBrightness));
+ }
+
+ @Test
public void testLoadingBrightnessConfigurations() {
String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<display-manager-state>\n"
@@ -374,7 +482,7 @@
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
newInjector.setReadStream(bais);
newDataStore.loadIfNeeded();
- assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+ assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */)));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index e6d3bbc..c4f4838 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -247,6 +248,7 @@
0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
verify(mBrightnessSetting).setBrightness(brightnessValue);
+ verify(mBrightnessSetting).setUserSerial(anyInt());
// Does nothing if the value is invalid
mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
@@ -358,4 +360,28 @@
verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
verify(mBrightnessSetting, never()).setBrightness(brightness);
}
+
+ @Test
+ public void testChangeBrightnessNitsWhenUserChanges() {
+ float brightnessValue1 = 0.3f;
+ float nits1 = 200f;
+ float brightnessValue2 = 0.5f;
+ float nits2 = 300f;
+ AutomaticBrightnessController automaticBrightnessController =
+ mock(AutomaticBrightnessController.class);
+ when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
+ when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2);
+ mDisplayBrightnessController.setAutomaticBrightnessController(
+ automaticBrightnessController);
+
+ mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
+ verify(mBrightnessSetting).setUserSerial(1);
+ verify(mBrightnessSetting).setBrightness(brightnessValue1);
+ verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
+
+ mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
+ verify(mBrightnessSetting).setUserSerial(2);
+ verify(mBrightnessSetting).setBrightness(brightnessValue2);
+ verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 4c10ddc..6668f85 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -87,7 +87,6 @@
// This list should be emptied! Items can be removed as bugs are fixed.
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
- .put(RemoteViews.class, "setIcon") // b/281018094
.put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
.put(Person.Builder.class, "setUri") // TODO: b/281044385
.put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index a422c68..dd90e04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -51,14 +51,18 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.DisplayShape;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
+import android.view.Surface;
+import android.view.WindowInsets;
import android.view.WindowInsets.Side;
import android.view.WindowManager;
@@ -388,6 +392,36 @@
&& displayPolicy.updateDecorInsetsInfo());
assertEquals(STATUS_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
di.logicalWidth, di.logicalHeight).mConfigInsets.top);
+
+ // Add a window that provides the same insets in current rotation. But it specifies
+ // different insets in other rotations.
+ final WindowState bar2 = createWindow(null, statusBar.mAttrs.type, "bar2");
+ bar2.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
+ .setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0))
+ };
+ bar2.mAttrs.setFitInsetsTypes(0);
+ bar2.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
+ final int doubleHeightFor90 = STATUS_BAR_HEIGHT * 2;
+ for (int i = ROTATION_0; i <= Surface.ROTATION_270; i++) {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.setFitInsetsTypes(0);
+ if (i == Surface.ROTATION_90) {
+ params.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(bar2, 0, WindowInsets.Type.statusBars())
+ .setInsetsSize(Insets.of(0, doubleHeightFor90, 0, 0))
+ };
+ } else {
+ params.providedInsets = bar2.mAttrs.providedInsets;
+ }
+ bar2.mAttrs.paramsForRotation[i] = params;
+ }
+ displayPolicy.addWindowLw(bar2, bar2.mAttrs);
+ // Current rotation is 0 and the top insets is still STATUS_BAR_HEIGHT, so no change.
+ assertFalse(displayPolicy.updateDecorInsetsInfo());
+ // The insets in other rotations should be still updated.
+ assertEquals(doubleHeightFor90, displayPolicy.getDecorInsetsInfo(Surface.ROTATION_90,
+ di.logicalHeight, di.logicalWidth).mConfigInsets.top);
}
@SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 863523f..ddc729f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -485,6 +485,7 @@
new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars())
.setInsetsSize(Insets.of(0, STATUS_BAR_HEIGHT, 0, 0))
};
+ statusBar.mAttrs.setFitInsetsTypes(0);
dc.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
return statusBar;
}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 2f96eec..4c55f7f 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -83,6 +83,7 @@
],
data: [
":FlickerTestApp",
+ "trace_config/*",
],
}
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 1176828..ec01317 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -4,6 +4,9 @@
-->
<configuration description="Runs WindowManager {MODULE}">
<option name="test-tag" value="FlickerTests" />
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on" />
@@ -29,25 +32,51 @@
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="run-command" value="settings put system show_touches 1" />
<option name="run-command" value="settings put system pointer_location 1" />
- <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
+ <option name="teardown-command"
+ value="settings delete secure show_ime_with_hard_keyboard" />
<option name="teardown-command" value="settings delete system show_touches" />
<option name="teardown-command" value="settings delete system pointer_location" />
- <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
+ <option name="teardown-command"
+ value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="{MODULE}.apk"/>
<option name="test-file-name" value="FlickerTestApp.apk" />
</target_preparer>
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file"
+ key="trace_config.textproto"
+ value="/data/misc/perfetto-traces/trace_config.textproto"
+ />
+ <!--Install the content provider automatically when we push some file in sdcard folder.-->
+ <!--Needed to avoid the installation during the test suite.-->
+ <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="{PACKAGE}"/>
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6600s" />
<option name="hidden-api-checks" value="false" />
+ <option name="device-listeners" value="android.device.collectors.PerfettoListener" />
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg"
+ key="perfetto_config_file"
+ value="trace_config.textproto"
+ />
+ <option name="instrumentation-arg" key="per_run" value="true" />
</test>
+ <!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/flicker" />
- <option name="collect-on-run-ended-only" value="true" />
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="(\w)+\.winscope" />
+ <option name="pull-pattern-keys" value="(\w)+\.mp4" />
+ <option name="collect-on-run-ended-only" value="false" />
<option name="clean-up" value="true" />
</metrics_collector>
</configuration>
diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
index 5b00310..de8a3c6 100644
--- a/tests/FlickerTests/manifests/AndroidManifest.xml
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -48,9 +48,4 @@
<uses-library android:name="android.test.runner"/>
<uses-library android:name="androidx.window.extensions" android:required="false"/>
</application>
-
- <!--<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="{{package}}"
- android:label="WindowManager Flicker Tests {MODULE}">
- </instrumentation>-->
</manifest>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 1f120d4..ed9e14f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -97,19 +97,19 @@
if (allStates) {
assertLayers {
this.invoke("entireScreenCovered") { entry ->
- entry.entry.displays.forEach { display ->
+ entry.entry.displays.filter { it.isOn }.forEach { display ->
entry.visibleRegion().coversAtLeast(display.layerStackSpace)
}
}
}
} else {
assertLayersStart {
- this.entry.displays.forEach { display ->
+ this.entry.displays.filter { it.isOn }.forEach { display ->
this.visibleRegion().coversAtLeast(display.layerStackSpace)
}
}
assertLayersEnd {
- this.entry.displays.forEach { display ->
+ this.entry.displays.filter { it.isOn }.forEach { display ->
this.visibleRegion().coversAtLeast(display.layerStackSpace)
}
}
diff --git a/tests/FlickerTests/trace_config/trace_config.textproto b/tests/FlickerTests/trace_config/trace_config.textproto
new file mode 100644
index 0000000..c9a35ac
--- /dev/null
+++ b/tests/FlickerTests/trace_config/trace_config.textproto
@@ -0,0 +1,77 @@
+# Copyright (C) 2023 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.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+}
+
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ name: "linux.ftrace"
+ ftrace_config {
+ ftrace_events: "ftrace/print"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
+ atrace_categories: "ss"
+ atrace_categories: "wm"
+ atrace_categories: "am"
+ atrace_categories: "aidl"
+ atrace_categories: "input"
+ atrace_categories: "binder_driver"
+ atrace_categories: "sched_process_exit"
+ atrace_apps: "com.android.server.wm.flicker"
+ atrace_apps: "com.android.server.wm.flicker.other"
+ atrace_apps: "com.android.server.wm.flicker.close"
+ atrace_apps: "com.android.server.wm.flicker.ime"
+ atrace_apps: "com.android.server.wm.flicker.launch"
+ atrace_apps: "com.android.server.wm.flicker.quickswitch"
+ atrace_apps: "com.android.server.wm.flicker.rotation"
+ atrace_apps: "com.android.server.wm.flicker.testapp"
+ atrace_apps: "com.android.systemui"
+ atrace_apps: "com.google.android.apps.nexuslauncher"
+ }
+ }
+}
+