Merge "Refactored NMS to handle user switch via the onUserSwitching method" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 3ab0934..255ec92 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -30,7 +30,7 @@
name: "framework-minus-apex.ravenwood-base",
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
- "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+ "@$(location :ravenwood-standard-options) " +
"--debug-log $(location hoststubgen_framework-minus-apex.log) " +
"--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
@@ -42,13 +42,13 @@
"--gen-input-dump-file $(location hoststubgen_dump.txt) " +
"--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
- "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " +
- "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+ "--policy-override-file $(location :ravenwood-framework-policies) " +
+ "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
srcs: [
":framework-minus-apex-for-hoststubgen",
- "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt",
- "ravenwood/texts/ravenwood-standard-options.txt",
- "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+ ":ravenwood-framework-policies",
+ ":ravenwood-standard-options",
+ ":ravenwood-annotation-allowed-classes",
],
out: [
"ravenwood.jar",
@@ -118,7 +118,7 @@
name: "services.core.ravenwood-base",
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
- "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
+ "@$(location :ravenwood-standard-options) " +
"--debug-log $(location hoststubgen_services.core.log) " +
"--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -130,13 +130,13 @@
"--gen-input-dump-file $(location hoststubgen_dump.txt) " +
"--in-jar $(location :services.core-for-hoststubgen) " +
- "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " +
- "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
+ "--policy-override-file $(location :ravenwood-services-policies) " +
+ "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
srcs: [
":services.core-for-hoststubgen",
- "ravenwood/texts/services.core-ravenwood-policies.txt",
- "ravenwood/texts/ravenwood-standard-options.txt",
- "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
+ ":ravenwood-services-policies",
+ ":ravenwood-standard-options",
+ ":ravenwood-annotation-allowed-classes",
],
out: [
"ravenwood.jar",
diff --git a/config/Android.bp b/config/Android.bp
index adce203..c9948c3 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,7 +33,7 @@
name: "preloaded-classes",
src: "preloaded-classes",
filename: "preloaded-classes",
- installable: false,
+ no_full_install: true,
}
filegroup {
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java
similarity index 71%
rename from core/java/android/app/admin/StringSetPolicyValue.java
rename to core/java/android/app/admin/PackageSetPolicyValue.java
index 12b11f4..8b253a2 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/PackageSetPolicyValue.java
@@ -28,18 +28,18 @@
/**
* @hide
*/
-public final class StringSetPolicyValue extends PolicyValue<Set<String>> {
+public final class PackageSetPolicyValue extends PolicyValue<Set<String>> {
- public StringSetPolicyValue(@NonNull Set<String> value) {
+ public PackageSetPolicyValue(@NonNull Set<String> value) {
super(value);
if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
- for (String str : value) {
- PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
+ for (String packageName : value) {
+ PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
}
}
}
- public StringSetPolicyValue(Parcel source) {
+ public PackageSetPolicyValue(Parcel source) {
this(readValues(source));
}
@@ -56,7 +56,7 @@
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- StringSetPolicyValue other = (StringSetPolicyValue) o;
+ PackageSetPolicyValue other = (PackageSetPolicyValue) o;
return Objects.equals(getValue(), other.getValue());
}
@@ -67,7 +67,7 @@
@Override
public String toString() {
- return "StringSetPolicyValue { " + getValue() + " }";
+ return "PackageNameSetPolicyValue { " + getValue() + " }";
}
@Override
@@ -84,16 +84,16 @@
}
@NonNull
- public static final Creator<StringSetPolicyValue> CREATOR =
- new Creator<StringSetPolicyValue>() {
+ public static final Creator<PackageSetPolicyValue> CREATOR =
+ new Creator<PackageSetPolicyValue>() {
@Override
- public StringSetPolicyValue createFromParcel(Parcel source) {
- return new StringSetPolicyValue(source);
+ public PackageSetPolicyValue createFromParcel(Parcel source) {
+ return new PackageSetPolicyValue(source);
}
@Override
- public StringSetPolicyValue[] newArray(int size) {
- return new StringSetPolicyValue[size];
+ public PackageSetPolicyValue[] newArray(int size) {
+ return new PackageSetPolicyValue[size];
}
};
}
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 7320cea..dede5b5 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -78,6 +78,11 @@
*
* <h3>Developer guide</h3>
* To learn more, read <a href="{@docRoot}work/dpc/system-updates">Manage system updates</a>.
+ * <p><strong>Note:</strong> <a href="https://source.android.com/docs/core/ota/modular-system">
+ * Google Play system updates</a> (also called Mainline updates) are automatically downloaded
+ * but require a device reboot to be installed. Refer to the mainline section in
+ * <a href="{@docRoot}work/dpc/system-updates#mainline">Manage system
+ * updates</a> for further details.</p>
*
* @see DevicePolicyManager#setSystemUpdatePolicy
* @see DevicePolicyManager#getSystemUpdatePolicy
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index af13011..b070742 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -753,6 +753,9 @@
@FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
checkNotUsed();
+ if (value == null) {
+ throw new IllegalArgumentException("Null AttributionSource not permitted.");
+ }
mBuilderFieldsSet |= 0x20;
mAttributionSourceState.next =
new AttributionSourceState[]{value.mAttributionSourceState};
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index fea2c25..9fe0bef 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -45,4 +45,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag{
+ name: "allow_disable_ipsec_loss_detector"
+ namespace: "vcn"
+ description: "Allow disabling IPsec packet loss detector"
+ bug: "336638836"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/tracing/inputmethod/InputMethodDataSource.java b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
new file mode 100644
index 0000000..5c5ad69
--- /dev/null
+++ b/core/java/android/tracing/inputmethod/InputMethodDataSource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.tracing.inputmethod;
+
+import android.annotation.NonNull;
+import android.tracing.perfetto.DataSource;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.StartCallbackArguments;
+import android.tracing.perfetto.StopCallbackArguments;
+import android.util.proto.ProtoInputStream;
+
+/**
+ * @hide
+ */
+public final class InputMethodDataSource
+ extends DataSource<DataSourceInstance, Void, Void> {
+ public static final String DATA_SOURCE_NAME = "android.inputmethod";
+
+ @NonNull
+ private final Runnable mOnStartCallback;
+ @NonNull
+ private final Runnable mOnStopCallback;
+
+ public InputMethodDataSource(@NonNull Runnable onStart, @NonNull Runnable onStop) {
+ super(DATA_SOURCE_NAME);
+ mOnStartCallback = onStart;
+ mOnStopCallback = onStop;
+ }
+
+ @Override
+ public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ return new DataSourceInstance(this, instanceIndex) {
+ @Override
+ protected void onStart(StartCallbackArguments args) {
+ mOnStartCallback.run();
+ }
+
+ @Override
+ protected void onStop(StopCallbackArguments args) {
+ mOnStopCallback.run();
+ }
+ };
+ }
+}
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 1afedc1..4530157 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -134,7 +134,9 @@
@Override
public void onBackInvoked() {
- if (!isBackAnimationAllowed()) {
+ if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) {
+ // play regular hide animation if back-animation is not allowed or if insets control has
+ // been cancelled by the system (this can happen in split screen for example)
mInsetsController.hide(ime());
return;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9579614..60ad926 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,7 +40,6 @@
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
import static android.view.flags.Flags.sensitiveContentAppProtection;
-import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly;
@@ -32230,7 +32229,7 @@
void increaseSensitiveViewsCount() {
if (mSensitiveViewsCount == 0) {
- mViewRootImpl.notifySensitiveContentAppProtection(true);
+ mViewRootImpl.addSensitiveContentAppProtection();
}
mSensitiveViewsCount++;
}
@@ -32238,11 +32237,7 @@
void decreaseSensitiveViewsCount() {
mSensitiveViewsCount--;
if (mSensitiveViewsCount == 0) {
- if (sensitiveContentPrematureProtectionRemovedFix()) {
- mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit();
- } else {
- mViewRootImpl.notifySensitiveContentAppProtection(false);
- }
+ mViewRootImpl.removeSensitiveContentAppProtection();
}
if (mSensitiveViewsCount < 0) {
Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ca8eaf9..fa57961 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -25,6 +25,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
+import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
@@ -4244,7 +4245,14 @@
mReportNextDraw = false;
mLastReportNextDrawReason = null;
mActiveSurfaceSyncGroup = null;
- mHasPendingTransactions = false;
+ if (mHasPendingTransactions) {
+ // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't
+ // merged with a sync group or BLASTBufferQueue before making it to this point
+ // But better a one or two frame flicker than steady-state broken from dropping
+ // whatever is in this transaction
+ mPendingTransaction.apply();
+ mHasPendingTransactions = false;
+ }
mSyncBuffer = false;
if (isInWMSRequestedSync()) {
mWmsRequestSyncGroup.markSyncReady();
@@ -4331,29 +4339,42 @@
* <li>It should only notify service to unblock projection when all sensitive view are
* removed from the window.
* </ol>
+ *
+ * @param enableProtection if true, the protection is enabled for this window.
+ * if false, the protection is removed for this window.
*/
- void notifySensitiveContentAppProtection(boolean showSensitiveContent) {
+ private void applySensitiveContentAppProtection(boolean enableProtection) {
try {
if (mSensitiveContentProtectionService == null) {
return;
}
if (DEBUG_SENSITIVE_CONTENT) {
Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName()
- + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent);
+ + ", token=" + getWindowToken() + ", flag=" + enableProtection);
}
// The window would be blocked during screen share if it shows sensitive content.
mSensitiveContentProtectionService.setSensitiveContentProtection(
- getWindowToken(), mContext.getPackageName(), showSensitiveContent);
+ getWindowToken(), mContext.getPackageName(), enableProtection);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to protect sensitive content during screen share", ex);
}
}
/**
- * Sensitive protection is removed on transaction commit to avoid prematurely removing
- * the protection.
+ * Add sensitive content protection, when there are one or more visible sensitive views.
*/
- void removeSensitiveContentProtectionOnTransactionCommit() {
+ void addSensitiveContentAppProtection() {
+ applySensitiveContentAppProtection(true);
+ }
+
+ /**
+ * Remove sensitive content protection, when there is no visible sensitive view.
+ */
+ void removeSensitiveContentAppProtection() {
+ if (!sensitiveContentPrematureProtectionRemovedFix()) {
+ applySensitiveContentAppProtection(false);
+ return;
+ }
if (DEBUG_SENSITIVE_CONTENT) {
Log.d(TAG, "Add transaction to remove sensitive content protection, package="
+ mContext.getPackageName() + ", token=" + getWindowToken());
@@ -4361,7 +4382,7 @@
Transaction t = new Transaction();
t.addTransactionCommittedListener(mExecutor, () -> {
if (mAttachInfo.mSensitiveViewsCount == 0) {
- notifySensitiveContentAppProtection(false);
+ applySensitiveContentAppProtection(false);
}
});
applyTransactionOnDraw(t);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 12bd45a..c0d31fa 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -54,7 +54,7 @@
is_fixed_read_only: true
metadata {
purpose: PURPOSE_BUGFIX
- }
+ }
}
flag {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0e7de48..a073873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2461,6 +2461,7 @@
* @hide
*/
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+ checkFocus();
final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
synchronized (mH) {
final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 5c113f8..461eab6 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@
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_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -93,6 +94,19 @@
@TaskFragmentTransitionType
public static final int TASK_FRAGMENT_TRANSIT_CHANGE = TRANSIT_CHANGE;
+
+ /**
+ * The task fragment drag resize transition used by activity embedding.
+ *
+ * This value is also used in Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE and must not
+ * conflict with other predefined transition types.
+ *
+ * @hide
+ */
+ @WindowManager.TransitionType
+ @TaskFragmentTransitionType
+ public static final int TASK_FRAGMENT_TRANSIT_DRAG_RESIZE = TRANSIT_FIRST_CUSTOM + 17;
+
/**
* Introduced a sub set of {@link WindowManager.TransitionType} for the types that are used for
* TaskFragment transition.
@@ -106,6 +120,7 @@
TASK_FRAGMENT_TRANSIT_OPEN,
TASK_FRAGMENT_TRANSIT_CLOSE,
TASK_FRAGMENT_TRANSIT_CHANGE,
+ TASK_FRAGMENT_TRANSIT_DRAG_RESIZE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface TaskFragmentTransitionType {}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 945164a..4d1b87a 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -120,4 +120,11 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "pip_restore_to_overlay"
+ description: "Restore exit-pip activity back to ActivityEmbedding overlay"
+ bug: "297887697"
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index ee9c3aa..cd4ccda 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -60,7 +60,9 @@
*/
public static ImeTracing getInstance() {
if (sInstance == null) {
- if (isSystemProcess()) {
+ if (android.tracing.Flags.perfettoIme()) {
+ sInstance = new ImeTracingPerfettoImpl();
+ } else if (isSystemProcess()) {
sInstance = new ImeTracingServerImpl();
} else {
sInstance = new ImeTracingClientImpl();
@@ -78,7 +80,7 @@
* and {@see #IME_TRACING_FROM_IMS}
* @param where
*/
- public void sendToService(byte[] protoDump, int source, String where) {
+ protected void sendToService(byte[] protoDump, int source, String where) {
InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
}
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
new file mode 100644
index 0000000..91b80dd
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodClientsTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodManagerServiceTraceProto;
+import android.internal.perfetto.protos.Inputmethodeditor.InputMethodServiceTraceProto;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.tracing.inputmethod.InputMethodDataSource;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An implementation of {@link ImeTracing} for perfetto tracing.
+ */
+final class ImeTracingPerfettoImpl extends ImeTracing {
+ private final AtomicInteger mTracingSessionsCount = new AtomicInteger(0);
+ private final AtomicBoolean mIsClientDumpInProgress = new AtomicBoolean(false);
+ private final AtomicBoolean mIsServiceDumpInProgress = new AtomicBoolean(false);
+ private final AtomicBoolean mIsManagerServiceDumpInProgress = new AtomicBoolean(false);
+ private final InputMethodDataSource mDataSource = new InputMethodDataSource(
+ mTracingSessionsCount::incrementAndGet,
+ mTracingSessionsCount::decrementAndGet);
+
+ ImeTracingPerfettoImpl() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(
+ new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT));
+ }
+
+
+ @Override
+ public void triggerClientDump(String where, InputMethodManager immInstance,
+ @Nullable byte[] icProto) {
+ if (!isEnabled() || !isAvailable()) {
+ return;
+ }
+
+ if (!mIsClientDumpInProgress.compareAndSet(false, true)) {
+ return;
+ }
+
+ if (immInstance == null) {
+ return;
+ }
+
+ try {
+ Trace.beginSection("inputmethod_client_dump");
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+ final long tokenWinscopeExtensions =
+ os.start(TracePacket.WINSCOPE_EXTENSIONS);
+ final long tokenExtensionsField =
+ os.start(WinscopeExtensionsImpl.INPUTMETHOD_CLIENTS);
+ os.write(InputMethodClientsTraceProto.WHERE, where);
+ final long tokenClient =
+ os.start(InputMethodClientsTraceProto.CLIENT);
+ immInstance.dumpDebug(os, icProto);
+ os.end(tokenClient);
+ os.end(tokenExtensionsField);
+ os.end(tokenWinscopeExtensions);
+ });
+ } finally {
+ mIsClientDumpInProgress.set(false);
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public void triggerServiceDump(String where,
+ @NonNull ServiceDumper dumper, @Nullable byte[] icProto) {
+ if (!isEnabled() || !isAvailable()) {
+ return;
+ }
+
+ if (!mIsServiceDumpInProgress.compareAndSet(false, true)) {
+ return;
+ }
+
+ try {
+ Trace.beginSection("inputmethod_service_dump");
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+ final long tokenWinscopeExtensions =
+ os.start(TracePacket.WINSCOPE_EXTENSIONS);
+ final long tokenExtensionsField =
+ os.start(WinscopeExtensionsImpl.INPUTMETHOD_SERVICE);
+ os.write(InputMethodServiceTraceProto.WHERE, where);
+ dumper.dumpToProto(os, icProto);
+ os.end(tokenExtensionsField);
+ os.end(tokenWinscopeExtensions);
+ });
+ } finally {
+ mIsServiceDumpInProgress.set(false);
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public void triggerManagerServiceDump(@NonNull String where, @NonNull ServiceDumper dumper) {
+ if (!isEnabled() || !isAvailable()) {
+ return;
+ }
+
+ if (!mIsManagerServiceDumpInProgress.compareAndSet(false, true)) {
+ return;
+ }
+
+ try {
+ Trace.beginSection("inputmethod_manager_service_dump");
+ mDataSource.trace((ctx) -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+ final long tokenWinscopeExtensions =
+ os.start(TracePacket.WINSCOPE_EXTENSIONS);
+ final long tokenExtensionsField =
+ os.start(WinscopeExtensionsImpl.INPUTMETHOD_MANAGER_SERVICE);
+ os.write(InputMethodManagerServiceTraceProto.WHERE, where);
+ dumper.dumpToProto(os, null);
+ os.end(tokenExtensionsField);
+ os.end(tokenWinscopeExtensions);
+ });
+ } finally {
+ mIsManagerServiceDumpInProgress.set(false);
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mTracingSessionsCount.get() > 0;
+ }
+
+ @Override
+ public void startTrace(@Nullable PrintWriter pw) {
+ // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+ }
+
+ @Override
+ public void stopTrace(@Nullable PrintWriter pw) {
+ // Intentionally left empty. Tracing start/stop is managed through Perfetto.
+ }
+
+ @Override
+ public void addToBuffer(ProtoOutputStream proto, int source) {
+ // Intentionally left empty. Only used for legacy tracing.
+ }
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index f2d2c1b..6ffa826 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -134,10 +134,12 @@
public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = 99;
public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = 100;
public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = 101;
+ public static final int CUJ_LAUNCHER_PRIVATE_SPACE_LOCK = 102;
+ public static final int CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK = 103;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
@VisibleForTesting
- static final int LAST_CUJ = CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+ static final int LAST_CUJ = CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK;
/** @hide */
@IntDef({
@@ -230,7 +232,9 @@
CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK,
CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK,
CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK,
- CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK
+ CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK,
+ CUJ_LAUNCHER_PRIVATE_SPACE_LOCK,
+ CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -335,6 +339,8 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_PICKER_SEARCH_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK;
}
private Cuj() {
@@ -533,6 +539,10 @@
return "LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK";
case CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK:
return "LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK";
+ case CUJ_LAUNCHER_PRIVATE_SPACE_LOCK:
+ return "LAUNCHER_PRIVATE_SPACE_LOCK";
+ case CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK:
+ return "LAUNCHER_PRIVATE_SPACE_UNLOCK";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 66b0158..0734e68 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -886,9 +886,16 @@
cellState.activationAnimator.cancel();
}
AnimatorSet animatorSet = new AnimatorSet();
+
+ // When running the line end animation (see doc for createLineEndAnimation), if cell is in:
+ // - activate state - use finger position at the time of hit detection
+ // - deactivate state - use current position where the end was last during initial animation
+ // Note that deactivate state will only come if mKeepDotActivated is themed true.
+ final float startX = activate == CELL_ACTIVATE ? mInProgressX : cellState.lineEndX;
+ final float startY = activate == CELL_ACTIVATE ? mInProgressY : cellState.lineEndY;
AnimatorSet.Builder animatorSetBuilder = animatorSet
.play(createLineDisappearingAnimation())
- .with(createLineEndAnimation(cellState, mInProgressX, mInProgressY,
+ .with(createLineEndAnimation(cellState, startX, startY,
getCenterXForColumn(cell.column), getCenterYForRow(cell.row)));
if (mDotSize != mDotSizeActivated) {
animatorSetBuilder.with(createDotRadiusAnimation(cellState));
diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index c00ebe4..57bbb1c 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -241,6 +241,23 @@
});
}
+ @Test
+ public void testOnBackInvokedHidesImeEvenIfInsetsControlCancelled() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // start back gesture
+ WindowInsetsAnimationControlListener animationControlListener = startBackGesture();
+
+ // simulate ImeBackAnimationController not receiving control (e.g. due to split screen)
+ animationControlListener.onCancelled(mWindowInsetsAnimationController);
+
+ // commit back gesture
+ mBackAnimationController.onBackInvoked();
+
+ // verify that InsetsController#hide is called
+ verify(mInsetsController, times(1)).hide(ime());
+ });
+ }
+
private WindowInsetsAnimationControlListener startBackGesture() {
// start back gesture
mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT));
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
index e62678f..423b55b 100644
--- a/data/keyboards/Android.bp
+++ b/data/keyboards/Android.bp
@@ -33,7 +33,7 @@
srcs: [
"*.kl",
],
- installable: false,
+ no_full_install: true,
}
prebuilt_usr_keychars {
@@ -41,7 +41,7 @@
srcs: [
"*.kcm",
],
- installable: false,
+ no_full_install: true,
}
prebuilt_usr_idc {
@@ -49,5 +49,5 @@
srcs: [
"*.idc",
],
- installable: false,
+ no_full_install: true,
}
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 59092d4..14388a6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -28,6 +28,7 @@
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE;
+import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
@@ -850,6 +851,14 @@
Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
return;
}
+
+ if (!parentInfo.isVisible()) {
+ // Only making the TaskContainer invisible and drops the other info, and perform the
+ // update when the next time the Task becomes visible.
+ taskContainer.setIsVisible(false);
+ return;
+ }
+
// Checks if container should be updated before apply new parentInfo.
final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
taskContainer.updateTaskFragmentParentInfo(parentInfo);
@@ -3243,6 +3252,7 @@
synchronized (mLock) {
final TransactionRecord transactionRecord =
mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE);
final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskContainer taskContainer = mTaskContainers.get(taskId);
if (taskContainer != null) {
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 67d34c7..a683738 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -151,6 +151,10 @@
return mIsVisible;
}
+ void setIsVisible(boolean visible) {
+ mIsVisible = visible;
+ }
+
boolean hasDirectActivity() {
return mHasDirectActivity;
}
@@ -185,13 +189,15 @@
boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
final Configuration configuration = info.getConfiguration();
- return info.isVisible()
- // No need to update presentation in PIP until the Task exit PIP.
- && !isInPictureInPicture(configuration)
- // If the task properties equals regardless of starting position, don't need to
- // update the container.
- && (mConfiguration.diffPublicOnly(configuration) != 0
- || mDisplayId != info.getDisplayId());
+ if (isInPictureInPicture(configuration)) {
+ // No need to update presentation in PIP until the Task exit PIP.
+ return false;
+ }
+
+ // If the task properties equals regardless of starting position, don't
+ // need to update the container.
+ return mConfiguration.diffPublicOnly(configuration) != 0
+ || mDisplayId != info.getDisplayId();
}
/**
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index fab298d..049a9e2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -580,7 +580,7 @@
final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
- false /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+ true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
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 a525877..7d86ec2 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
@@ -1619,6 +1619,48 @@
verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
}
+ @Test
+ public void testTaskFragmentParentInfoChanged() {
+ // Making a split
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+ // Updates the parent info.
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final Configuration configuration = new Configuration();
+ final TaskFragmentParentInfo originalInfo = new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */);
+ mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+ TASK_ID, originalInfo);
+ assertTrue(taskContainer.isVisible());
+
+ // Making a public configuration change while the Task is invisible.
+ configuration.densityDpi += 100;
+ final TaskFragmentParentInfo invisibleInfo = new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, false /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */);
+ mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+ TASK_ID, invisibleInfo);
+
+ // Ensure the TaskContainer is inivisible, but the configuration is not updated.
+ assertFalse(taskContainer.isVisible());
+ assertTrue(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+ configuration) > 0);
+
+ // Updates when Task to become visible
+ final TaskFragmentParentInfo visibleInfo = new TaskFragmentParentInfo(configuration,
+ DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+ null /* decorSurface */);
+ mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
+ TASK_ID, visibleInfo);
+
+ // Ensure the Task is visible and configuration is updated.
+ assertTrue(taskContainer.isVisible());
+ assertFalse(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly(
+ configuration) > 0);
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d44033c..a426b20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -26,6 +26,7 @@
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
import android.animation.Animator;
import android.animation.ValueAnimator;
@@ -190,6 +191,10 @@
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+ if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+ // Jump cut for AE drag resizing because the content is veiled.
+ return new ArrayList<>();
+ }
boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 1f9358e..d6b9d34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
import static java.util.Objects.requireNonNull;
@@ -90,6 +91,12 @@
/** Whether ActivityEmbeddingController should animate this transition. */
public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) {
+ // The TRANSIT_TASK_FRAGMENT_DRAG_RESIZE type happens when the user drags the
+ // interactive divider to resize the split containers. The content is veiled, so we will
+ // handle the transition with a jump cut.
+ return true;
+ }
boolean containsEmbeddingChange = false;
for (TransitionInfo.Change change : info.getChanges()) {
if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index a87116e..607a3b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -185,7 +185,7 @@
nextTarget = snapAlgorithm.getDismissStartTarget();
}
if (nextTarget != null) {
- mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget);
+ mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), nextTarget);
return true;
}
return super.performAccessibilityAction(host, action, args);
@@ -345,9 +345,9 @@
mMoving = true;
}
if (mMoving) {
- final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
mLastDraggingPosition = position;
- mSplitLayout.updateDivideBounds(position);
+ mSplitLayout.updateDividerBounds(position);
}
break;
case MotionEvent.ACTION_UP:
@@ -363,7 +363,7 @@
final float velocity = isLeftRightSplit
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
- final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos;
final DividerSnapAlgorithm.SnapTarget snapTarget =
mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
mSplitLayout.snapToTarget(position, snapTarget);
@@ -472,12 +472,12 @@
mInteractive = interactive;
mHideHandle = hideHandle;
if (!mInteractive && mHideHandle && mMoving) {
- final int position = mSplitLayout.getDividePosition();
- mSplitLayout.flingDividePosition(
+ final int position = mSplitLayout.getDividerPosition();
+ mSplitLayout.flingDividerPosition(
mLastDraggingPosition,
position,
mSplitLayout.FLING_RESIZE_DURATION,
- () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+ () -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */));
mMoving = false;
}
releaseTouching();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 6b2d544..2ea32f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -78,7 +78,7 @@
/**
* Records and handles layout of splits. Helps to calculate proper bounds when configuration or
- * divide position changes.
+ * divider position changes.
*/
public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
private static final String TAG = "SplitLayout";
@@ -278,7 +278,7 @@
return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl();
}
- int getDividePosition() {
+ int getDividerPosition() {
return mDividerPosition;
}
@@ -489,20 +489,20 @@
public void setDividerAtBorder(boolean start) {
final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
: mDividerSnapAlgorithm.getDismissEndTarget().position;
- setDividePosition(pos, false /* applyLayoutChange */);
+ setDividerPosition(pos, false /* applyLayoutChange */);
}
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
*/
- void updateDivideBounds(int position) {
+ void updateDividerBounds(int position) {
updateBounds(position);
mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
mSurfaceEffectPolicy.mParallaxOffset.y);
}
- void setDividePosition(int position, boolean applyLayoutChange) {
+ void setDividerPosition(int position, boolean applyLayoutChange) {
mDividerPosition = position;
updateBounds(mDividerPosition);
if (applyLayoutChange) {
@@ -511,14 +511,14 @@
}
/**
- * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+ * Updates divider position and split bounds base on the ratio within root bounds. Falls back
* to middle position if the provided SnapTarget is not supported.
*/
public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
snapPosition);
- setDividePosition(snapTarget != null
+ setDividerPosition(snapTarget != null
? snapTarget.position
: mDividerSnapAlgorithm.getMiddleTarget().position,
false /* applyLayoutChange */);
@@ -546,24 +546,24 @@
}
/**
- * Sets new divide position and updates bounds correspondingly. Notifies listener if the new
+ * Sets new divider position and updates bounds correspondingly. Notifies listener if the new
* target indicates dismissing split.
*/
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.snapPosition) {
case SNAP_TO_START_AND_DISMISS:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
case SNAP_TO_END_AND_DISMISS:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
- () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
+ flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
}
@@ -615,19 +615,19 @@
public void flingDividerToDismiss(boolean toEnd, int reason) {
final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
: mDividerSnapAlgorithm.getDismissStartTarget().position;
- flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
+ flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
}
/** Fling divider from current position to center position. */
public void flingDividerToCenter() {
final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
- flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
- () -> setDividePosition(pos, true /* applyLayoutChange */));
+ flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION,
+ () -> setDividerPosition(pos, true /* applyLayoutChange */));
}
@VisibleForTesting
- void flingDividePosition(int from, int to, int duration,
+ void flingDividerPosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
if (from == to) {
if (flingFinishedCallback != null) {
@@ -647,7 +647,7 @@
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mDividerFlingAnimator.addUpdateListener(
- animation -> updateDivideBounds((int) animation.getAnimatedValue()));
+ animation -> updateDividerBounds((int) animation.getAnimatedValue()));
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index cf3ad42..713d04bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -194,6 +194,10 @@
return mHideSizeCompatRestartButtonTolerance;
}
+ int getDefaultHideRestartButtonTolerance() {
+ return MAX_PERCENTAGE_VAL;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 4e5c2fa..f195f95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -20,11 +20,11 @@
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AppCompatTaskInfo;
import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -219,14 +219,30 @@
@VisibleForTesting
boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
- if (!Flags.allowHideScmButton()) {
+ // Always show button if display is phone sized.
+ if (!Flags.allowHideScmButton() || taskInfo.configuration.smallestScreenWidthDp
+ < LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) {
return true;
}
- final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
- appCompatTaskInfo.topActivityLetterboxHeight);
- final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+
+ final int letterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth;
+ final int letterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight;
+ final Rect stableBounds = getTaskStableBounds();
+ final int appWidth = stableBounds.width();
+ final int appHeight = stableBounds.height();
+ // App is floating, should always show restart button.
+ if (appWidth > letterboxWidth && appHeight > letterboxHeight) {
+ return true;
+ }
+ // If app fills the width of the display, don't show restart button (for landscape apps)
+ // if device has a custom tolerance value.
+ if (mHideScmTolerance != mCompatUIConfiguration.getDefaultHideRestartButtonTolerance()
+ && appWidth == letterboxWidth) {
+ return false;
+ }
+
+ final int letterboxArea = letterboxWidth * letterboxHeight;
+ final int taskArea = appWidth * appHeight;
if (letterboxArea == 0 || taskArea == 0) {
return false;
}
@@ -234,13 +250,6 @@
return percentageAreaOfLetterboxInTask < mHideScmTolerance;
}
- private int computeArea(int width, int height) {
- if (width == 0 || height == 0) {
- return 0;
- }
- return width * height;
- }
-
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 6aad4e2..8df287d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -69,7 +69,9 @@
default void onSplitVisibilityChanged(boolean visible) {}
}
- /** Callback interface for listening to requests to enter split select */
+ /**
+ * Callback interface for listening to requests to enter split select. Used for desktop -> split
+ */
interface SplitSelectListener {
default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
int splitPosition, Rect taskBounds) {
@@ -90,6 +92,24 @@
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+ interface SplitInvocationListener {
+ /**
+ * Called whenever shell starts or stops the split screen animation
+ * @param animationRunning if {@code true} the animation has begun, if {@code false} the
+ * animation has finished
+ */
+ default void onSplitAnimationInvoked(boolean animationRunning) { }
+ }
+
+ /**
+ * Registers a {@link SplitInvocationListener} to notify when the animation to enter split
+ * screen has started and stopped
+ *
+ * @param executor callbacks to the listener will be executed on this executor
+ */
+ void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+ @NonNull Executor executor);
+
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 547457b..b9d70e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1166,6 +1166,12 @@
}
@Override
+ public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener,
+ @NonNull Executor executor) {
+ mStageCoordinator.registerSplitAnimationListener(listener, executor);
+ }
+
+ @Override
public void onFinishedWakingUp() {
mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
}
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 1a53a1d..6e5b767 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
@@ -55,6 +55,7 @@
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
/** Manages transition animations for split-screen. */
class SplitScreenTransitions {
@@ -79,6 +80,8 @@
private Transitions.TransitionFinishCallback mFinishCallback = null;
private SurfaceControl.Transaction mFinishTransaction;
+ private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+ private Executor mSplitInvocationListenerExecutor;
SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
@NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) {
@@ -353,6 +356,10 @@
+ " skip to start enter split transition since it already exist. ");
return null;
}
+ if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) {
+ mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener
+ .onSplitAnimationInvoked(true /*animationRunning*/));
+ }
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
return transition;
@@ -457,6 +464,7 @@
mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
+ mStageCoordinator.notifySplitAnimationFinished();
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
} else if (isPendingDismiss(transition)) {
mPendingDismiss.onConsumed(aborted);
@@ -529,6 +537,12 @@
mTransitions.getAnimExecutor().execute(va::start);
}
+ public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener,
+ @NonNull Executor executor) {
+ mSplitInvocationListener = listener;
+ mSplitInvocationListenerExecutor = executor;
+ }
+
/** Calls when the transition got consumed. */
interface TransitionConsumedCallback {
void onConsumed(boolean aborted);
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 5e9451a..b10176d 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
@@ -157,6 +157,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -237,6 +238,9 @@
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
private SplitRequest mSplitRequest;
+ /** Used to notify others of when shell is animating into split screen */
+ private SplitScreen.SplitInvocationListener mSplitInvocationListener;
+ private Executor mSplitInvocationListenerExecutor;
/**
* Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
@@ -247,6 +251,14 @@
return false;
}
+ /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */
+ public void registerSplitAnimationListener(
+ @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) {
+ mSplitInvocationListener = listener;
+ mSplitInvocationListenerExecutor = executor;
+ mSplitTransitions.registerSplitAnimListener(listener, executor);
+ }
+
class SplitRequest {
@SplitPosition
int mActivatePosition;
@@ -535,7 +547,7 @@
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
Log.w(TAG, splitFailureMessage("startShortcut",
"side stage was not populated"));
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
}
if (finishedCallback != null) {
@@ -666,7 +678,7 @@
null /* childrenToTop */, EXIT_REASON_UNKNOWN));
Log.w(TAG, splitFailureMessage("startIntentLegacy",
"side stage was not populated"));
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
}
if (apps != null) {
@@ -1287,7 +1299,7 @@
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
"main or side stage was not populated."));
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
} else {
mSyncQueue.queue(evictWct);
mSyncQueue.runInSync(t -> {
@@ -1308,7 +1320,7 @@
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
"main or side stage was not populated"));
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
return;
}
@@ -2890,6 +2902,7 @@
if (hasEnteringPip) {
mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
startTransaction, finishTransaction, finishCallback);
+ notifySplitAnimationFinished();
return true;
}
@@ -2924,6 +2937,7 @@
// the transition, or synchronize task-org callbacks.
}
// Use normal animations.
+ notifySplitAnimationFinished();
return false;
} else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
// A display-change has been un-expectedly inserted into the transition. Redirect
@@ -2937,6 +2951,7 @@
mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
}
+ notifySplitAnimationFinished();
return true;
}
}
@@ -3110,7 +3125,7 @@
pendingEnter.mRemoteHandler.onTransitionConsumed(transition,
false /*aborted*/, finishT);
}
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
return true;
}
}
@@ -3139,6 +3154,7 @@
final TransitionInfo.Change finalMainChild = mainChild;
final TransitionInfo.Change finalSideChild = sideChild;
enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
+ notifySplitAnimationFinished();
if (finalMainChild != null) {
if (!mainNotContainOpenTask) {
mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
@@ -3560,6 +3576,19 @@
mSplitLayout.isLeftRightSplit());
}
+ private void handleUnsupportedSplitStart() {
+ mSplitUnsupportedToast.show();
+ notifySplitAnimationFinished();
+ }
+
+ void notifySplitAnimationFinished() {
+ if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) {
+ return;
+ }
+ mSplitInvocationListenerExecutor.execute(() ->
+ mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/));
+ }
+
/**
* Logs the exit of splitscreen to a specific stage. This must be called before the exit is
* executed.
@@ -3622,7 +3651,7 @@
if (!ENABLE_SHELL_TRANSITIONS) {
StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
return;
}
@@ -3642,7 +3671,7 @@
"app package " + taskInfo.baseActivity.getPackageName()
+ " does not support splitscreen, or is a controlled activity type"));
if (splitScreenVisible) {
- mSplitUnsupportedToast.show();
+ handleUnsupportedSplitStart();
}
}
}
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 9b2922d..4d3c763 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
@@ -61,6 +61,7 @@
import android.view.WindowManager;
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
+import android.window.TaskFragmentOrganizer;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionMetrics;
@@ -183,6 +184,13 @@
/** Transition to resize PiP task. */
public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+ /**
+ * The task fragment drag resize transition used by activity embedding.
+ */
+ public static final int TRANSIT_TASK_FRAGMENT_DRAG_RESIZE =
+ // TRANSIT_FIRST_CUSTOM + 17
+ TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE;
+
private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 0f24bb5..b8a19ad 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -13,3 +13,5 @@
pbdr@google.com
tkachenkoi@google.com
mpodolian@google.com
+jeremysim@google.com
+peanutbutter@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 2ac72af..ea522cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -20,6 +20,8 @@
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -100,6 +102,20 @@
}
@Test
+ public void testTransitionTypeDragResize() {
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .build();
+ final Animator animator = mAnimRunner.createAnimator(
+ info, mStartTransaction, mFinishTransaction,
+ () -> mFinishCallback.onTransitionFinished(null /* wct */),
+ new ArrayList());
+
+ // The animation should be empty when it is a jump cut for drag resize.
+ assertEquals(0, animator.getDuration());
+ }
+
+ @Test
public void testInvalidCustomAnimation() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
.addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 56d0f8e1..8de60b7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -115,27 +115,27 @@
@Test
public void testUpdateDivideBounds() {
- mSplitLayout.updateDivideBounds(anyInt());
+ mSplitLayout.updateDividerBounds(anyInt());
verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
anyInt());
}
@Test
public void testSetDividePosition() {
- mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
- assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+ mSplitLayout.setDividerPosition(100, false /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividerPosition()).isEqualTo(100);
verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
- mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
- assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
+ mSplitLayout.setDividerPosition(200, true /* applyLayoutChange */);
+ assertThat(mSplitLayout.getDividerPosition()).isEqualTo(200);
verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
}
@Test
public void testSetDivideRatio() {
- mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
+ mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
mSplitLayout.setDivideRatio(SNAP_TO_50_50);
- assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+ assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@@ -152,7 +152,7 @@
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
SNAP_TO_START_AND_DISMISS);
- mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+ mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
}
@@ -164,7 +164,7 @@
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
SNAP_TO_END_AND_DISMISS);
- mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
+ mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
}
@@ -188,7 +188,7 @@
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+ verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(),
mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 5209d0e..41a81c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -22,6 +22,7 @@
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -98,14 +99,28 @@
private CompatUIWindowManager mWindowManager;
private TaskInfo mTaskInfo;
+ private DisplayLayout mDisplayLayout;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = TASK_WIDTH;
+ displayInfo.logicalHeight = TASK_HEIGHT;
+ mDisplayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+ final InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ final InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, TASK_HEIGHT - 200, TASK_WIDTH, TASK_HEIGHT);
+ insetsState.addSource(insetsSource);
+ mDisplayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
- mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
mCompatUIConfiguration, mOnRestartButtonClicked);
spyOn(mWindowManager);
@@ -363,9 +378,9 @@
// Update if the insets change on the existing display layout
clearInvocations(mWindowManager);
- InsetsState insetsState = new InsetsState();
+ final InsetsState insetsState = new InsetsState();
insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
- InsetsSource insetsSource = new InsetsSource(
+ final InsetsSource insetsSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
insetsSource.setFrame(0, 1800, 1000, 2000);
insetsState.addSource(insetsSource);
@@ -493,16 +508,14 @@
@Test
public void testShouldShowSizeCompatRestartButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
-
- doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+ doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
- mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
mCompatUIConfiguration, mOnRestartButtonClicked);
// Simulate rotation of activity in square display
TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
- taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
- taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -512,11 +525,21 @@
assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
// Simulate folding
- taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
- assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+ final InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, 1000, TASK_HEIGHT));
+ final InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, TASK_HEIGHT - 200, 1000, TASK_HEIGHT);
+ insetsState.addSource(insetsSource);
+ mDisplayLayout.setInsets(mContext.getResources(), insetsState);
+ mWindowManager.updateDisplayLayout(mDisplayLayout);
+ taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 100;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
- taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
- taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+ // Simulate floating app with 90& area, more than tolerance
+ taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 950;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1900;
assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
}
@@ -529,10 +552,10 @@
cameraCompatControlState;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
// Letterboxed activity that takes half the screen should show size compat restart button
- taskInfo.configuration.windowConfiguration.setBounds(
- new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ // Screen width dp larger than a normal phone.
+ taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
return taskInfo;
}
}
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 befc702..34b2eeb 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
@@ -39,10 +39,13 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -63,6 +66,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -105,6 +109,8 @@
@Mock private ShellExecutor mMainExecutor;
@Mock private LaunchAdjacentController mLaunchAdjacentController;
@Mock private DefaultMixedHandler mMixedHandler;
+ @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
+ private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -147,6 +153,7 @@
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager();
doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager();
+ mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor);
}
@Test
@@ -452,6 +459,15 @@
mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */);
}
+ @Test
+ @UiThreadTest
+ public void testSplitInvocationCallback() {
+ enterSplit();
+ mTestShellExecutor.flushAll();
+ verify(mInvocationListener, times(1))
+ .onSplitAnimationInvoked(eq(true));
+ }
+
private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f9dc5fa..933a33e 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -272,7 +272,10 @@
if (it == mLocked.spotControllers.end()) {
mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
}
- mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
+ bool skipScreenshot = mLocked.displaysToSkipScreenshot.find(displayId) !=
+ mLocked.displaysToSkipScreenshot.end();
+ mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits,
+ skipScreenshot);
}
void PointerController::clearSpots() {
@@ -352,6 +355,17 @@
mCursorController.setCustomPointerIcon(icon);
}
+void PointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+ if (!mEnabled) return;
+
+ std::scoped_lock lock(getLock());
+ if (skip) {
+ mLocked.displaysToSkipScreenshot.insert(displayId);
+ } else {
+ mLocked.displaysToSkipScreenshot.erase(displayId);
+ }
+}
+
void PointerController::doInactivityTimeout() {
fade(Transition::GRADUAL);
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 6ee5707..d76ca5d 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -67,6 +67,7 @@
void clearSpots() override;
void updatePointerIcon(PointerIconStyle iconId) override;
void setCustomPointerIcon(const SpriteIcon& icon) override;
+ void setSkipScreenshot(int32_t displayId, bool skip) override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -115,6 +116,7 @@
std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
+ std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot;
} mLocked GUARDED_BY(getLock());
class DisplayInfoListener : public gui::WindowInfosListener {
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index a63453d..0baa929 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -129,7 +129,7 @@
update.state.surfaceVisible = false;
update.state.surfaceControl =
obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight,
- update.state.displayId);
+ update.state.displayId, update.state.skipScreenshot);
if (update.state.surfaceControl != NULL) {
update.surfaceChanged = surfaceChanged = true;
}
@@ -209,7 +209,7 @@
(update.state.dirty &
(DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
- DIRTY_DRAW_DROP_SHADOW))))) {
+ DIRTY_DRAW_DROP_SHADOW | DIRTY_SKIP_SCREENSHOT))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -260,6 +260,14 @@
t.setLayer(update.state.surfaceControl, surfaceLayer);
}
+ if (wantSurfaceVisibleAndDrawn &&
+ (becomingVisible || (update.state.dirty & DIRTY_SKIP_SCREENSHOT))) {
+ int32_t flags =
+ update.state.skipScreenshot ? ISurfaceComposerClient::eSkipScreenshot : 0;
+ t.setFlags(update.state.surfaceControl, flags,
+ ISurfaceComposerClient::eSkipScreenshot);
+ }
+
if (becomingVisible) {
t.show(update.state.surfaceControl);
@@ -332,8 +340,8 @@
}
}
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
- int32_t displayId) {
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId,
+ bool hideOnMirrored) {
ensureSurfaceComposerClient();
const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
@@ -341,11 +349,13 @@
ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
}
+ int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
+ if (hideOnMirrored) {
+ createFlags |= ISurfaceComposerClient::eSkipScreenshot;
+ }
const sp<SurfaceControl> surfaceControl =
mSurfaceComposerClient->createSurface(String8("Sprite"), width, height,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eHidden |
- ISurfaceComposerClient::eCursorWindow,
+ PIXEL_FORMAT_RGBA_8888, createFlags,
parent ? parent->getHandle() : nullptr);
if (surfaceControl == nullptr || !surfaceControl->isValid()) {
ALOGE("Error creating sprite surface.");
@@ -474,6 +484,15 @@
}
}
+void SpriteController::SpriteImpl::setSkipScreenshot(bool skip) {
+ AutoMutex _l(mController.mLock);
+
+ if (mLocked.state.skipScreenshot != skip) {
+ mLocked.state.skipScreenshot = skip;
+ invalidateLocked(DIRTY_SKIP_SCREENSHOT);
+ }
+}
+
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
bool wasDirty = mLocked.state.dirty;
mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 35776e9..4e4ba65 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -96,6 +96,10 @@
/* Sets the id of the display where the sprite should be shown. */
virtual void setDisplayId(int32_t displayId) = 0;
+
+ /* Sets the flag to hide sprite on mirrored displays.
+ * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
+ virtual void setSkipScreenshot(bool skip) = 0;
};
/*
@@ -152,6 +156,7 @@
DIRTY_DISPLAY_ID = 1 << 7,
DIRTY_ICON_STYLE = 1 << 8,
DIRTY_DRAW_DROP_SHADOW = 1 << 9,
+ DIRTY_SKIP_SCREENSHOT = 1 << 10,
};
/* Describes the state of a sprite.
@@ -182,6 +187,7 @@
int32_t surfaceHeight;
bool surfaceDrawn;
bool surfaceVisible;
+ bool skipScreenshot;
inline bool wantSurfaceVisible() const {
return visible && alpha > 0.0f && icon.isValid();
@@ -209,6 +215,7 @@
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
virtual void setDisplayId(int32_t displayId);
+ virtual void setSkipScreenshot(bool skip);
inline const SpriteState& getStateLocked() const {
return mLocked.state;
@@ -272,7 +279,8 @@
void doDisposeSurfaces();
void ensureSurfaceComposerClient();
- sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId);
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId,
+ bool hideOnMirrored);
};
} // namespace android
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 99952aa..530d541 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,12 +40,13 @@
// --- Spot ---
void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
- int32_t displayId) {
+ int32_t displayId, bool skipScreenshot) {
sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
sprite->setAlpha(alpha);
sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
sprite->setPosition(newX, newY);
sprite->setDisplayId(displayId);
+ sprite->setSkipScreenshot(skipScreenshot);
x = newX;
y = newY;
@@ -84,7 +85,7 @@
}
void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits) {
+ BitSet32 spotIdBits, bool skipScreenshot) {
#if DEBUG_SPOT_UPDATES
ALOGD("setSpots: idBits=%08x", spotIdBits.value);
for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
@@ -116,7 +117,7 @@
spot = createAndAddSpotLocked(id, mLocked.displaySpots);
}
- spot->updateSprite(&icon, x, y, mDisplayId);
+ spot->updateSprite(&icon, x, y, mDisplayId, skipScreenshot);
}
for (Spot* spot : mLocked.displaySpots) {
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 5bbc75d..608653c 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -32,7 +32,7 @@
TouchSpotController(int32_t displayId, PointerControllerContext& context);
~TouchSpotController();
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits);
+ BitSet32 spotIdBits, bool skipScreenshot);
void clearSpots();
void reloadSpotResources();
@@ -59,7 +59,8 @@
y(0.0f),
mLastIcon(nullptr) {}
- void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId);
+ void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId,
+ bool skipScreenshot);
void dump(std::string& out, const char* prefix = "") const;
private:
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index a1bb5b3..fcf226c 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -372,6 +372,45 @@
<< "The pointer display changes to invalid when PointerController is destroyed.";
}
+TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) {
+ ensureDisplayViewportIsSet();
+
+ PointerCoords testSpotCoords;
+ testSpotCoords.clear();
+ testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 1);
+ testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
+ BitSet32 testIdBits;
+ testIdBits.markBit(0);
+ std::array<uint32_t, MAX_POINTER_ID + 1> testIdToIndex;
+
+ sp<MockSprite> testSpotSprite(new NiceMock<MockSprite>);
+
+ // By default sprite is not marked secure
+ EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testSpotSprite));
+ EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
+
+ // Update spots to sync state with sprite
+ mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+ ADISPLAY_ID_DEFAULT);
+ testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+
+ // Marking the display to skip screenshot should update sprite as well
+ mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
+ EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
+
+ // Update spots to sync state with sprite
+ mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+ ADISPLAY_ID_DEFAULT);
+ testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+
+ // Reset flag and verify again
+ mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
+ EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
+ mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
+ ADISPLAY_ID_DEFAULT);
+ testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
+}
+
class PointerControllerWindowInfoListenerTest : public Test {};
TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 013b79c..0867221 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -34,6 +34,7 @@
MOCK_METHOD(void, setAlpha, (float), (override));
MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+ MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
};
} // namespace android
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 554fe5e..b2838c8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2064,24 +2064,31 @@
}
/**
- * Transfers to a given route for the remote session. The given route must be included in
- * {@link RoutingSessionInfo#getTransferableRoutes()}.
+ * Attempts a transfer to a {@link RoutingSessionInfo#getTransferableRoutes() transferable
+ * route}.
*
+ * <p>Transferring to a transferable route does not require the app to transfer the playback
+ * state from one route to the other. The route provider completely manages the transfer. An
+ * example of provider-managed transfers are the switches between the system's routes, like
+ * the built-in speakers and a BT headset.
+ *
+ * @return True if the transfer is handled by this controller, or false if a new controller
+ * should be created instead.
* @see RoutingSessionInfo#getSelectedRoutes()
* @see RoutingSessionInfo#getTransferableRoutes()
* @see ControllerCallback#onControllerUpdated
*/
- void transferToRoute(@NonNull MediaRoute2Info route) {
+ boolean tryTransferWithinProvider(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
synchronized (mControllerLock) {
if (isReleased()) {
Log.w(TAG, "transferToRoute: Called on released controller. Ignoring.");
- return;
+ return true;
}
if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) {
Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route);
- return;
+ return false;
}
}
@@ -2096,6 +2103,7 @@
Log.e(TAG, "Unable to transfer to route for session.", ex);
}
}
+ return true;
}
/**
@@ -3587,20 +3595,14 @@
}
RoutingController controller = getCurrentController();
- if (controller
- .getRoutingSessionInfo()
- .getTransferableRoutes()
- .contains(route.getId())) {
- controller.transferToRoute(route);
- return;
+ if (!controller.tryTransferWithinProvider(route)) {
+ requestCreateController(
+ controller,
+ route,
+ MANAGER_REQUEST_ID_NONE,
+ Process.myUserHandle(),
+ mContext.getPackageName());
}
-
- requestCreateController(
- controller,
- route,
- MANAGER_REQUEST_ID_NONE,
- Process.myUserHandle(),
- mContext.getPackageName());
}
@Override
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 46a5138..0bae63a 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,7 +24,7 @@
<string name="string_cancel">Cancel</string>
<!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
<string name="string_continue">Continue</string>
- <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+ <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=30] -->
<string name="string_more_options">Save another way</string>
<!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
<string name="string_learn_more">Learn more</string>
@@ -174,4 +174,4 @@
<!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
<string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
<string name="more_options_content_description">More</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a7b7da5..30bec77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -649,6 +649,9 @@
for (CachedBluetoothDevice cbd : mMemberDevices) {
cbd.setName(name);
}
+ if (mSubDevice != null) {
+ mSubDevice.setName(name);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 9bf42f9..ed964a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -100,6 +100,7 @@
// device.
if (hearingAidDevice != null) {
hearingAidDevice.setSubDevice(newDevice);
+ newDevice.setName(hearingAidDevice.getName());
return true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index 8e3df8b..2de2174 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -48,17 +48,15 @@
private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history";
private static final String BT_HEARING_AIDS_CONNECTED_HISTORY =
"bt_hearing_aids_connected_history";
- private static final String BT_HEARING_DEVICES_PAIRED_HISTORY =
+ private static final String BT_HEARABLE_DEVICES_PAIRED_HISTORY =
"bt_hearing_devices_paired_history";
- private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY =
+ private static final String BT_HEARABLE_DEVICES_CONNECTED_HISTORY =
"bt_hearing_devices_connected_history";
- private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category";
-
private static final String HISTORY_RECORD_DELIMITER = ",";
static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";
- static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser";
- static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser";
+ static final String CATEGORY_HEARABLE_DEVICES = "A11yHearingDevicesUser";
+ static final String CATEGORY_NEW_HEARABLE_DEVICES = "A11yNewHearingDevicesUser";
static final int PAIRED_HISTORY_EXPIRED_DAY = 30;
static final int CONNECTED_HISTORY_EXPIRED_DAY = 7;
@@ -73,14 +71,14 @@
HistoryType.TYPE_UNKNOWN,
HistoryType.TYPE_HEARING_AIDS_PAIRED,
HistoryType.TYPE_HEARING_AIDS_CONNECTED,
- HistoryType.TYPE_HEARING_DEVICES_PAIRED,
- HistoryType.TYPE_HEARING_DEVICES_CONNECTED})
+ HistoryType.TYPE_HEARABLE_DEVICES_PAIRED,
+ HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED})
public @interface HistoryType {
int TYPE_UNKNOWN = -1;
int TYPE_HEARING_AIDS_PAIRED = 0;
int TYPE_HEARING_AIDS_CONNECTED = 1;
- int TYPE_HEARING_DEVICES_PAIRED = 2;
- int TYPE_HEARING_DEVICES_CONNECTED = 3;
+ int TYPE_HEARABLE_DEVICES_PAIRED = 2;
+ int TYPE_HEARABLE_DEVICES_CONNECTED = 3;
}
private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>();
@@ -127,8 +125,8 @@
}
/**
- * Updates corresponding history if we found the device is a hearing device after profile state
- * changed.
+ * Updates corresponding history if we found the device is a hearing related device after
+ * profile state changed.
*
* @param context the request context
* @param cachedDevice the remote device
@@ -148,7 +146,7 @@
} else if (cachedDevice.getProfiles().stream().anyMatch(
p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
}
removeFromJustBonded(cachedDevice.getAddress());
}
@@ -161,7 +159,7 @@
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
} else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) {
HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
}
}
}
@@ -169,18 +167,13 @@
/**
* Returns the user category if the user is already categorized. Otherwise, checks the
* history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS},
- * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and
- * {@link #CATEGORY_NEW_HEARING_DEVICES}.
+ * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARABLE_DEVICES}, and
+ * {@link #CATEGORY_NEW_HEARABLE_DEVICES}.
*
* @param context the request context
* @return the category which user belongs to
*/
public static synchronized String getUserCategory(Context context) {
- String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, "");
- if (!userCategory.isEmpty()) {
- return userCategory;
- }
-
LinkedList<Long> hearingAidsConnectedHistory = getHistory(context,
HistoryType.TYPE_HEARING_AIDS_CONNECTED);
if (hearingAidsConnectedHistory != null
@@ -192,29 +185,29 @@
// will be categorized as CATEGORY_HEARING_AIDS.
if (hearingAidsPairedHistory != null
&& hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
- userCategory = CATEGORY_NEW_HEARING_AIDS;
+ return CATEGORY_NEW_HEARING_AIDS;
} else {
- userCategory = CATEGORY_HEARING_AIDS;
+ return CATEGORY_HEARING_AIDS;
}
}
- LinkedList<Long> hearingDevicesConnectedHistory = getHistory(context,
- HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
- if (hearingDevicesConnectedHistory != null
- && hearingDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
- LinkedList<Long> hearingDevicesPairedHistory = getHistory(context,
- HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+ LinkedList<Long> hearableDevicesConnectedHistory = getHistory(context,
+ HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED);
+ if (hearableDevicesConnectedHistory != null
+ && hearableDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) {
+ LinkedList<Long> hearableDevicesPairedHistory = getHistory(context,
+ HistoryType.TYPE_HEARABLE_DEVICES_PAIRED);
// Since paired history will be cleared after 30 days. If there's any record within 30
- // days, the user will be categorized as CATEGORY_NEW_HEARING_DEVICES. Otherwise, the
- // user will be categorized as CATEGORY_HEARING_DEVICES.
- if (hearingDevicesPairedHistory != null
- && hearingDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
- userCategory = CATEGORY_NEW_HEARING_DEVICES;
+ // days, the user will be categorized as CATEGORY_NEW_HEARABLE_DEVICES. Otherwise, the
+ // user will be categorized as CATEGORY_HEARABLE_DEVICES.
+ if (hearableDevicesPairedHistory != null
+ && hearableDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) {
+ return CATEGORY_NEW_HEARABLE_DEVICES;
} else {
- userCategory = CATEGORY_HEARING_DEVICES;
+ return CATEGORY_HEARABLE_DEVICES;
}
}
- return userCategory;
+ return "";
}
/**
@@ -245,7 +238,7 @@
}
/**
- * Adds current timestamp into BT hearing devices related history.
+ * Adds current timestamp into BT hearing related devices history.
* @param context the request context
* @param type the type of history to store the data. See {@link HistoryType}.
*/
@@ -279,13 +272,13 @@
static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) {
String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type);
if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName)
- || BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) {
+ || BT_HEARABLE_DEVICES_PAIRED_HISTORY.equals(spName)) {
LinkedList<Long> history = convertToHistoryList(
getSharedPreferences(context).getString(spName, ""));
removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY);
return history;
} else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName)
- || BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) {
+ || BT_HEARABLE_DEVICES_CONNECTED_HISTORY.equals(spName)) {
LinkedList<Long> history = convertToHistoryList(
getSharedPreferences(context).getString(spName, ""));
removeRecordsBeforeDay(history, CONNECTED_HISTORY_EXPIRED_DAY);
@@ -352,9 +345,9 @@
HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY);
HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
- HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY);
+ HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, BT_HEARABLE_DEVICES_PAIRED_HISTORY);
HISTORY_TYPE_TO_SP_NAME_MAPPING.put(
- HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY);
+ HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, BT_HEARABLE_DEVICES_CONNECTED_HISTORY);
}
private HearingAidStatsLogUtils() {}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b356f54..b4bd482 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1703,6 +1703,30 @@
}
@Test
+ public void setName_memberDeviceNameIsSet() {
+ when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+ when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ mCachedDevice.setName(DEVICE_ALIAS);
+
+ verify(mDevice).setAlias(DEVICE_ALIAS);
+ verify(mSubDevice).setAlias(DEVICE_ALIAS);
+ }
+
+ @Test
+ public void setName_subDeviceNameIsSet() {
+ when(mDevice.getAlias()).thenReturn(DEVICE_NAME);
+ when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME);
+
+ mCachedDevice.setSubDevice(mSubCachedDevice);
+ mCachedDevice.setName(DEVICE_ALIAS);
+
+ verify(mDevice).setAlias(DEVICE_ALIAS);
+ verify(mSubDevice).setAlias(DEVICE_ALIAS);
+ }
+
+ @Test
public void getProfileConnectionState_nullProfile_returnDisconnected() {
assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo(
BluetoothProfile.STATE_DISCONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 1f0da90..4188d2e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -299,6 +299,7 @@
mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2);
assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2);
+ verify(mDevice2).setAlias(DEVICE_ALIAS_1);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
index bd5a022..cd16721 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
@@ -145,20 +145,29 @@
}
@Test
- public void getUserCategory_hearingDevicesUser() {
- prepareHearingDevicesUserHistory();
+ public void getUserCategory_hearableDevicesUser() {
+ prepareHearableDevicesUserHistory();
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
- HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
+ HearingAidStatsLogUtils.CATEGORY_HEARABLE_DEVICES);
}
@Test
- public void getUserCategory_newHearingDevicesUser() {
- prepareHearingDevicesUserHistory();
+ public void getUserCategory_newHearableDevicesUser() {
+ prepareHearableDevicesUserHistory();
prepareNewUserHistory();
assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
- HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
+ HearingAidStatsLogUtils.CATEGORY_NEW_HEARABLE_DEVICES);
+ }
+
+ @Test
+ public void getUserCategory_bothHearingAidsAndHearableDevicesUser_returnHearingAidsUser() {
+ prepareHearingAidsUserHistory();
+ prepareHearableDevicesUserHistory();
+
+ assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+ HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
}
private long convertToStartOfDayTime(long timestamp) {
@@ -176,12 +185,12 @@
}
}
- private void prepareHearingDevicesUserHistory() {
+ private void prepareHearableDevicesUserHistory() {
final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
HearingAidStatsLogUtils.addToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, data);
}
}
@@ -191,6 +200,6 @@
HearingAidStatsLogUtils.addToHistory(mContext,
HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
HearingAidStatsLogUtils.addToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, data);
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ed62ce7..65c5708 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -362,7 +362,6 @@
"device_state_flags_lib",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
- "kotlinx_coroutines_guava",
"//frameworks/libs/systemui:iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
@@ -383,7 +382,6 @@
"androidx.compose.material_material-icons-extended",
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
- "device_policy_aconfig_flags_lib",
],
libs: [
"keepanno-annotations",
@@ -543,7 +541,6 @@
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
"TraceurCommon",
- "kotlinx_coroutines_guava",
],
}
@@ -625,7 +622,6 @@
"//frameworks/libs/systemui:compilelib",
"SystemUI-tests-base",
"androidx.compose.runtime_runtime",
- "SystemUI-core",
],
libs: [
"keepanno-annotations",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0c89a5d..deab818 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -59,13 +59,16 @@
]
}
],
-
+
"auto-end-to-end-postsubmit": [
{
"name": "AndroidAutomotiveHomeTests",
"options" : [
{
"include-filter": "android.platform.tests.HomeTest"
+ },
+ {
+ "exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget"
}
]
},
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index fd90bd9..de090f4 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -581,6 +581,16 @@
}
flag {
+ name: "contextual_tips_assistant_dismiss_fix"
+ namespace: "systemui"
+ description: "Improve assistant dismiss signal accuracy for contextual tips."
+ bug: "334759504"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "shaderlib_loading_effect_refactor"
namespace: "systemui"
description: "Extend shader library to provide the common loading effects."
@@ -796,7 +806,7 @@
name: "dream_input_session_pilfer_once"
namespace: "systemui"
description: "Pilfer at most once per input session"
- bug: "324600132"
+ bug: "333596426"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index addcaf4..04ac748 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -38,8 +38,9 @@
defaults: ["AndroidLintCheckerTestDefaults"],
srcs: ["tests/**/*.kt"],
data: [
- ":framework",
":androidx.annotation_annotation",
+ ":dagger2",
+ ":framework",
":kotlinx-coroutines-core",
],
static_libs: [
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
new file mode 100644
index 0000000..68ec1ee
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+/**
+ * Prevents binding Activities, Services, and BroadcastReceivers as Singletons in the Dagger graph.
+ *
+ * It is OK to mark a BroadcastReceiver as singleton as long as it is being constructed/injected and
+ * registered directly in the code. If instead it is declared in the manifest, and we let Android
+ * construct it for us, we also need to let Android destroy it for us, so don't allow marking it as
+ * singleton.
+ */
+class SingletonAndroidComponentDetector : Detector(), SourceCodeScanner {
+ override fun applicableAnnotations(): List<String> {
+ return listOf(
+ "com.android.systemui.dagger.SysUISingleton",
+ )
+ }
+
+ override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean =
+ type == AnnotationUsageType.DEFINITION
+
+ override fun visitAnnotationUsage(
+ context: JavaContext,
+ element: UElement,
+ annotationInfo: AnnotationInfo,
+ usageInfo: AnnotationUsageInfo
+ ) {
+ if (element !is UAnnotation) {
+ return
+ }
+
+ val parent = element.uastParent ?: return
+
+ if (isInvalidBindingMethod(parent)) {
+ context.report(
+ ISSUE,
+ element,
+ context.getLocation(element),
+ "Do not bind Activities, Services, or BroadcastReceivers as Singleton."
+ )
+ } else if (isInvalidClassDeclaration(parent)) {
+ context.report(
+ ISSUE,
+ element,
+ context.getLocation(element),
+ "Do not mark Activities or Services as Singleton."
+ )
+ }
+ }
+
+ private fun isInvalidBindingMethod(parent: UElement): Boolean {
+ if (parent !is UMethod) {
+ return false
+ }
+
+ if (
+ parent.returnType?.canonicalText !in
+ listOf(
+ "android.app.Activity",
+ "android.app.Service",
+ "android.content.BroadcastReceiver",
+ )
+ ) {
+ return false
+ }
+
+ if (
+ !MULTIBIND_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } } &&
+ !MULTIPROVIDE_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } }
+ ) {
+ return false
+ }
+ return true
+ }
+
+ private fun isInvalidClassDeclaration(parent: UElement): Boolean {
+ if (parent !is UClass) {
+ return false
+ }
+
+ if (
+ parent.javaPsi.superClass?.qualifiedName !in
+ listOf(
+ "android.app.Activity",
+ "android.app.Service",
+ // Fine to mark BroadcastReceiver as singleton in this scenario
+ )
+ ) {
+ return false
+ }
+
+ return true
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "SingletonAndroidComponent",
+ briefDescription = "Activity, Service, or BroadcastReceiver marked as Singleton",
+ explanation =
+ """Activities, Services, and BroadcastReceivers are created and destroyed by
+ the Android System Server. Marking them with a Dagger scope
+ results in them being cached and reused by Dagger. Trying to reuse a
+ component like this will make for a very bad time.""",
+ category = Category.CORRECTNESS,
+ priority = 10,
+ severity = Severity.ERROR,
+ moreInfo =
+ "https://developer.android.com/guide/components/activities/process-lifecycle",
+ // Note that JAVA_FILE_SCOPE also includes Kotlin source files.
+ implementation =
+ Implementation(
+ SingletonAndroidComponentDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ private val MULTIBIND_ANNOTATIONS =
+ listOf("dagger.Binds", "dagger.multibindings.IntoMap", "dagger.multibindings.ClassKey")
+
+ val MULTIPROVIDE_ANNOTATIONS =
+ listOf(
+ "dagger.Provides",
+ "dagger.multibindings.IntoMap",
+ "dagger.multibindings.ClassKey"
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index e93264c..cecbc47 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -40,6 +40,7 @@
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
+ SingletonAndroidComponentDetector.ISSUE,
StaticSettingsProviderDetector.ISSUE,
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index e1cca88..8396f3f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -21,8 +21,9 @@
internal val libraryNames =
arrayOf(
- "framework.jar",
"androidx.annotation_annotation.jar",
+ "dagger2.jar",
+ "framework.jar",
"kotlinx-coroutines-core.jar",
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
new file mode 100644
index 0000000..0606af8
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class SingletonAndroidComponentDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = SingletonAndroidComponentDetector()
+
+ override fun getIssues(): List<Issue> = listOf(SingletonAndroidComponentDetector.ISSUE)
+
+ @Test
+ fun testBindsServiceAsSingleton() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.app.Service
+ import com.android.systemui.dagger.SysUISingleton
+ import dagger.Binds
+ import dagger.Module
+ import dagger.multibindings.ClassKey
+ import dagger.multibindings.IntoMap
+
+ @Module
+ interface BadModule {
+ @SysUISingleton
+ @Binds
+ @IntoMap
+ @ClassKey(SingletonService::class)
+ fun bindSingletonService(service: SingletonService): Service
+ }
+ """
+ .trimIndent()
+ ),
+ *stubs
+ )
+ .issues(SingletonAndroidComponentDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+ @SysUISingleton
+ ~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ )
+ }
+
+ @Test
+ fun testProvidesBroadcastReceiverAsSingleton() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.content.BroadcastReceiver
+ import com.android.systemui.dagger.SysUISingleton
+ import dagger.Provides
+ import dagger.Module
+ import dagger.multibindings.ClassKey
+ import dagger.multibindings.IntoMap
+
+ @Module
+ abstract class BadModule {
+ @SysUISingleton
+ @Provides
+ @IntoMap
+ @ClassKey(SingletonBroadcastReceiver::class)
+ fun providesSingletonBroadcastReceiver(br: SingletonBroadcastReceiver): BroadcastReceiver {
+ return br
+ }
+ }
+ """
+ .trimIndent()
+ ),
+ *stubs
+ )
+ .issues(SingletonAndroidComponentDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent]
+ @SysUISingleton
+ ~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ )
+ }
+ @Test
+ fun testMarksActivityAsSingleton() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.app.Activity
+ import com.android.systemui.dagger.SysUISingleton
+
+ @SysUISingleton
+ class BadActivity : Activity() {
+ }
+ """
+ .trimIndent()
+ ),
+ *stubs
+ )
+ .issues(SingletonAndroidComponentDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/BadActivity.kt:6: Error: Do not mark Activities or Services as Singleton. [SingletonAndroidComponent]
+ @SysUISingleton
+ ~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """
+ .trimIndent()
+ )
+ }
+ @Test
+ fun testMarksBroadcastReceiverAsSingleton() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.content.BroadcastReceiver
+ import com.android.systemui.dagger.SysUISingleton
+
+ @SysUISingleton
+ class SingletonReceveiver : BroadcastReceiver() {
+ }
+ """
+ .trimIndent()
+ ),
+ *stubs
+ )
+ .issues(SingletonAndroidComponentDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ // Define stubs for Android imports. The tests don't run on Android so
+ // they don't "see" any of Android specific classes. We need to define
+ // the method parameters for proper resolution.
+ private val singletonStub: TestFile =
+ java(
+ """
+ package com.android.systemui.dagger;
+
+ public @interface SysUISingleton {
+ }
+ """
+ )
+
+ private val stubs = arrayOf(singletonStub) + androidStubs
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
new file mode 100644
index 0000000..dc58919
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.session.shared
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** Data store for [Session][com.android.systemui.scene.session.ui.composable.Session]. */
+class SessionStorage {
+ private var _storage by mutableStateOf(hashMapOf<String, StorageEntry>())
+
+ /**
+ * Data store containing all state retained for invocations of
+ * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+ */
+ val storage: MutableMap<String, StorageEntry>
+ get() = _storage
+
+ /**
+ * Storage for an individual invocation of
+ * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession]
+ */
+ class StorageEntry(val keys: Array<out Any?>, var stored: Any?)
+
+ /** Clears the data store; any downstream usage within `@Composable`s will be recomposed. */
+ fun clear() {
+ _storage = hashMapOf()
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
new file mode 100644
index 0000000..924aa54
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.session.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.currentCompositeKeyHash
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.mapSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import com.android.systemui.scene.session.shared.SessionStorage
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard
+ * [remember][androidx.compose.runtime.remember] invocations to ones that are preserved beyond the
+ * callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ * val session = remember { Session() }
+ * ...
+ * if (someCondition) {
+ * Child(session)
+ * }
+ * }
+ *
+ * @Composable
+ * fun Child(session: Session) {
+ * val state by session.rememberSession { mutableStateOf(0f) }
+ * ...
+ * }
+ * ```
+ */
+interface Session {
+ /**
+ * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had
+ * in the previous composition, otherwise produce and remember a new value by calling [init].
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+ * to reset and [init] to be rerun
+ * @param key An optional key to be used as a key for the saved value. If `null`, we use the one
+ * automatically generated by the Compose runtime which is unique for the every exact code
+ * location in the composition tree
+ * @param init A factory function to create the initial value of this state
+ * @see androidx.compose.runtime.remember
+ */
+ @Composable fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T
+}
+
+/** Returns a new [Session], optionally backed by the provided [SessionStorage]. */
+fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(storage)
+
+/**
+ * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had in
+ * the previous composition, otherwise produce and remember a new value by calling [init].
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ * reset and [init] to be rerun
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ * one automatically generated by the Compose runtime which is unique for the every exact code
+ * location in the composition tree
+ * @param init A factory function to create the initial value of this state
+ * @see androidx.compose.runtime.remember
+ */
+@Composable
+fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T =
+ rememberSession(key, inputs, init = init)
+
+/**
+ * An explicit storage for remembering composable state outside of the lifetime of a composition.
+ *
+ * Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that
+ * are preserved beyond the callsite's existence in the composition.
+ *
+ * ```kotlin
+ * @Composable
+ * fun Parent() {
+ * val session = rememberSaveableSession()
+ * ...
+ * if (someCondition) {
+ * Child(session)
+ * }
+ * }
+ *
+ * @Composable
+ * fun Child(session: SaveableSession) {
+ * val state by session.rememberSaveableSession { mutableStateOf(0f) }
+ * ...
+ * }
+ * ```
+ */
+interface SaveableSession : Session {
+ /**
+ * Remember the value produced by [init].
+ *
+ * It behaves similarly to [rememberSession], but the stored value will survive the activity or
+ * process recreation using the saved instance state mechanism (for example it happens when the
+ * screen is rotated in the Android application).
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state
+ * to reset and [init] to be rerun
+ * @param saver The [Saver] object which defines how the state is saved and restored.
+ * @param key An optional key to be used as a key for the saved value. If not provided we use
+ * the automatically generated by the Compose runtime which is unique for the every exact code
+ * location in the composition tree
+ * @param init A factory function to create the initial value of this state
+ * @see rememberSaveable
+ */
+ @Composable
+ fun <T : Any> rememberSaveableSession(
+ vararg inputs: Any?,
+ saver: Saver<T, out Any>,
+ key: String?,
+ init: () -> T,
+ ): T
+}
+
+/**
+ * Returns a new [SaveableSession] that is preserved across configuration changes.
+ *
+ * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
+ * reset.
+ * @param key An optional key to be used as a key for the saved value. If not provided we use the
+ * automatically generated by the Compose runtime which is unique for the every exact code
+ * location in the composition tree.
+ */
+@Composable
+fun rememberSaveableSession(
+ vararg inputs: Any?,
+ key: String? = null,
+): SaveableSession =
+ rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
+
+private class SessionImpl(
+ private val storage: SessionStorage = SessionStorage(),
+) : Session {
+ @Composable
+ override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T {
+ val storage = storage.storage
+ val compositeKey = currentCompositeKeyHash
+ // key is the one provided by the user or the one generated by the compose runtime
+ val finalKey =
+ if (!key.isNullOrEmpty()) {
+ key
+ } else {
+ compositeKey.toString(MAX_SUPPORTED_RADIX)
+ }
+ if (finalKey !in storage) {
+ val value = init()
+ SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) }
+ return value
+ }
+ val entry = storage[finalKey]!!
+ if (!inputs.contentEquals(entry.keys)) {
+ val value = init()
+ SideEffect { entry.stored = value }
+ return value
+ }
+ @Suppress("UNCHECKED_CAST") return entry.stored as T
+ }
+}
+
+private class SaveableSessionImpl(
+ saveableStorage: MutableMap<String, StorageEntry> = mutableMapOf(),
+ sessionStorage: SessionStorage = SessionStorage(),
+) : SaveableSession, Session by Session(sessionStorage) {
+
+ var saveableStorage: MutableMap<String, StorageEntry> by mutableStateOf(saveableStorage)
+
+ @Composable
+ override fun <T : Any> rememberSaveableSession(
+ vararg inputs: Any?,
+ saver: Saver<T, out Any>,
+ key: String?,
+ init: () -> T,
+ ): T {
+ val compositeKey = currentCompositeKeyHash
+ // key is the one provided by the user or the one generated by the compose runtime
+ val finalKey =
+ if (!key.isNullOrEmpty()) {
+ key
+ } else {
+ compositeKey.toString(MAX_SUPPORTED_RADIX)
+ }
+
+ @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)
+
+ if (finalKey !in saveableStorage) {
+ val value = init()
+ SideEffect { saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) }
+ return value
+ }
+ when (val entry = saveableStorage[finalKey]!!) {
+ is StorageEntry.Unrestored -> {
+ val value = saver.restore(entry.unrestored) ?: init()
+ SideEffect {
+ saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+ }
+ return value
+ }
+ is StorageEntry.Restored<*> -> {
+ if (!inputs.contentEquals(entry.inputs)) {
+ val value = init()
+ SideEffect {
+ saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
+ }
+ return value
+ }
+ @Suppress("UNCHECKED_CAST") return entry.stored as T
+ }
+ }
+ }
+
+ sealed class StorageEntry {
+ class Unrestored(val unrestored: Any) : StorageEntry()
+
+ class Restored<T>(val inputs: Array<out Any?>, var stored: T, val saver: Saver<T, Any>) :
+ StorageEntry() {
+ fun SaverScope.saveEntry() {
+ with(saver) { stored?.let { save(it) } }
+ }
+ }
+ }
+
+ object SessionSaver :
+ Saver<SaveableSessionImpl, Any> by mapSaver(
+ save = { sessionScope: SaveableSessionImpl ->
+ sessionScope.saveableStorage.mapValues { (k, v) ->
+ when (v) {
+ is StorageEntry.Unrestored -> v.unrestored
+ is StorageEntry.Restored<*> -> {
+ with(v) { saveEntry() }
+ }
+ }
+ }
+ },
+ restore = { savedMap: Map<String, Any?> ->
+ SaveableSessionImpl(
+ saveableStorage =
+ savedMap.mapValuesNotNullTo(mutableMapOf()) { (k, v) ->
+ v?.let { StorageEntry.Unrestored(v) }
+ }
+ )
+ }
+ )
+}
+
+private const val MAX_SUPPORTED_RADIX = 36
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
index 00225fc..0f6d51d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -54,6 +54,13 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val slice by viewModel.buttonSlice.collectAsState()
val label = stringResource(R.string.volume_panel_noise_control_title)
+ val isClickable = viewModel.isClickable(slice)
+ val onClick =
+ if (isClickable) {
+ { ancPopup.show(null) }
+ } else {
+ null
+ }
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
@@ -69,8 +76,9 @@
}
.clip(RoundedCornerShape(28.dp)),
slice = slice,
+ isEnabled = onClick != null,
onWidthChanged = viewModel::onButtonSliceWidthChanged,
- onClick = { ancPopup.show(null) }
+ onClick = onClick,
)
Text(
modifier = Modifier.clearAndSetSemantics {},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index 74af3ca..fc5d212 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -32,6 +32,7 @@
fun SliceAndroidView(
slice: Slice?,
modifier: Modifier = Modifier,
+ isEnabled: Boolean = true,
onWidthChanged: ((Int) -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
@@ -40,7 +41,6 @@
factory = { context: Context ->
ClickableSliceView(
ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel),
- onClick,
)
.apply {
mode = SliceView.MODE_LARGE
@@ -50,12 +50,14 @@
if (onWidthChanged != null) {
addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged))
}
- if (onClick != null) {
- setOnClickListener { onClick() }
- }
}
},
- update = { sliceView: SliceView -> sliceView.slice = slice }
+ update = { sliceView: ClickableSliceView ->
+ sliceView.slice = slice
+ sliceView.onClick = onClick
+ sliceView.isEnabled = isEnabled
+ sliceView.isClickable = isEnabled
+ }
)
}
@@ -86,10 +88,9 @@
* first.
*/
@SuppressLint("ViewConstructor") // only used in this class
-private class ClickableSliceView(
- context: Context,
- private val onClick: (() -> Unit)?,
-) : SliceView(context) {
+private class ClickableSliceView(context: Context) : SliceView(context) {
+
+ var onClick: (() -> Unit)? = null
init {
if (onClick != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 874c0a2..12debbc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -91,7 +92,8 @@
},
onClick = { onCheckedChange(!viewModel.isActive) },
shape = RoundedCornerShape(28.dp),
- colors = colors
+ colors = colors,
+ contentPadding = PaddingValues(0.dp)
) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 7f94f0d..4e3a032 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -117,6 +117,9 @@
coroutineScope: CoroutineScope,
transitionKey: TransitionKey? = null,
): TransitionState.Transition?
+
+ /** Immediately snap to the given [scene]. */
+ fun snapToScene(scene: SceneKey)
}
/**
@@ -745,6 +748,17 @@
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
setTargetScene(scene, coroutineScope = this)
}
+
+ override fun snapToScene(scene: SceneKey) {
+ // Force finish all transitions.
+ while (currentTransitions.isNotEmpty()) {
+ val transition = transitionStates[0] as TransitionState.Transition
+ finishTransition(transition, transition.currentScene)
+ }
+
+ check(transitionStates.size == 1)
+ transitionStates[0] = TransitionState.Idle(scene)
+ }
}
private const val TAG = "SceneTransitionLayoutState"
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f29d0a7..d2c8bd6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -635,4 +635,19 @@
Log.setWtfHandler(originalHandler)
}
}
+
+ @Test
+ fun snapToScene() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutState(SceneA)
+
+ // Transition to B.
+ state.setTargetScene(SceneB, coroutineScope = this)
+ val transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasCurrentScene(SceneB)
+
+ // Snap to C.
+ state.snapToScene(SceneC)
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneC)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index c8717d8..447c280 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -17,8 +17,6 @@
package com.android.keyguard
-import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
import android.content.res.Configuration
import android.media.AudioManager
import android.telephony.TelephonyManager
@@ -150,7 +148,6 @@
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var postureController: DevicePostureController
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Captor
private lateinit var swipeListenerArgumentCaptor:
@@ -276,7 +273,6 @@
mSelectedUserInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
- devicePolicyManager,
keyguardTransitionInteractor,
{ primaryBouncerInteractor },
) {
@@ -938,45 +934,6 @@
verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
}
- @Test
- fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() {
- mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
- val mainUserId = 10
-
- underTest.showMessageForFailedUnlockAttempt(
- /* userId = */ mainUserId,
- /* expiringUserId = */ mainUserId,
- /* mainUserId = */ mainUserId,
- /* remainingBeforeWipe = */ 1,
- /* failedAttempts = */ 1
- )
-
- verify(view)
- .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY))
- }
-
- @Test
- fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() {
- mSetFlagsRule.enableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
- val secondaryUserId = 10
- val mainUserId = 0
-
- underTest.showMessageForFailedUnlockAttempt(
- /* userId = */ secondaryUserId,
- /* expiringUserId = */ secondaryUserId,
- /* mainUserId = */ mainUserId,
- /* remainingBeforeWipe = */ 1,
- /* failedAttempts = */ 1
- )
-
- verify(view)
- .showAlmostAtWipeDialog(
- any(),
- any(),
- eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER)
- )
- }
-
private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
get() {
underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
new file mode 100644
index 0000000..c0d481c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class OneHandedModeRepositoryImplTest : SysuiTestCase() {
+
+ private val testUser1 = UserHandle.of(1)!!
+ private val testUser2 = UserHandle.of(2)!!
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val settings: FakeSettings = FakeSettings()
+
+ private val underTest: OneHandedModeRepository =
+ OneHandedModeRepositoryImpl(
+ testDispatcher,
+ scope.backgroundScope,
+ settings,
+ )
+
+ @Test
+ fun isEnabled_settingNotInitialized_returnsFalseByDefault() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ runCurrent()
+
+ assertThat(actualValue).isFalse()
+ }
+
+ @Test
+ fun isEnabled_initiallyGetsSettingsValue() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+ runCurrent()
+
+ assertThat(actualValue).isTrue()
+ }
+
+ @Test
+ fun isEnabled_settingUpdated_valueUpdated() =
+ scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+ runCurrent()
+ assertThat(actualValue).isFalse()
+
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+ runCurrent()
+
+ assertThat(actualValue).isTrue()
+ runCurrent()
+
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ runCurrent()
+ assertThat(actualValue).isFalse()
+ }
+
+ @Test
+ fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
+ scope.runTest {
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
+ runCurrent()
+ assertThat(lastValueUser1).isFalse()
+ assertThat(lastValueUser2).isFalse()
+
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
+ runCurrent()
+ assertThat(lastValueUser1).isTrue()
+ assertThat(lastValueUser2).isFalse()
+ }
+
+ @Test
+ fun setEnabled() =
+ scope.runTest {
+ val success = underTest.setIsEnabled(true, testUser1)
+ runCurrent()
+ assertThat(success).isTrue()
+
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+ assertThat(actualValue).isEqualTo(ENABLED)
+ }
+
+ @Test
+ fun setDisabled() =
+ scope.runTest {
+ val success = underTest.setIsEnabled(false, testUser1)
+ runCurrent()
+ assertThat(success).isTrue()
+
+ val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier)
+ assertThat(actualValue).isEqualTo(DISABLED)
+ }
+
+ companion object {
+ private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 0c5e726..81878aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -17,8 +17,6 @@
package com.android.systemui.authentication.domain.interactor
import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -34,8 +32,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -414,16 +410,12 @@
}
@Test
- @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
fun upcomingWipe() =
testScope.runTest {
val upcomingWipe by collectLastValue(underTest.upcomingWipe)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
- kosmos.fakeUserRepository.asMainUser()
- kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe =
- FakeUserRepository.MAIN_USER_ID
underTest.authenticate(correctPin)
assertThat(upcomingWipe).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index 31b67b4..f52c66e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -16,36 +16,57 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val repository = kosmos.fakeKeyguardTransitionRepository
- val shadeRepository = kosmos.fakeShadeRepository
+ val shadeTestUtil by lazy { kosmos.shadeTestUtil }
val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
- val underTest = kosmos.aodToLockscreenTransitionViewModel
+ lateinit var underTest: AodToLockscreenTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.aodToLockscreenTransitionViewModel
+ }
@Test
fun deviceEntryParentViewShows() =
@@ -65,7 +86,7 @@
testScope.runTest {
val alpha by collectLastValue(underTest.notificationAlpha)
- shadeRepository.setQsExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
runCurrent()
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
@@ -81,7 +102,7 @@
testScope.runTest {
val alpha by collectLastValue(underTest.notificationAlpha)
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
runCurrent()
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index bef9515..e3ae3ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -16,13 +16,14 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -32,31 +33,53 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToAodTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
private val repository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
- private val underTest = kosmos.lockscreenToAodTransitionViewModel
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ lateinit var underTest: LockscreenToAodTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.lockscreenToAodTransitionViewModel
+ }
@Test
fun backgroundViewAlpha_shadeNotExpanded() =
@@ -195,11 +218,11 @@
private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
} else {
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- shadeRepository.setQsExpansion(0f)
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 8f04ec38..adeb395 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,24 +18,25 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
@@ -45,10 +46,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -56,14 +59,27 @@
}
private val testScope = kosmos.testScope
private lateinit var repository: FakeKeyguardTransitionRepository
- private lateinit var shadeRepository: ShadeRepository
+ private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var underTest: LockscreenToDreamingTransitionViewModel
+ // add to init block
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
repository = kosmos.fakeKeyguardTransitionRepository
- shadeRepository = kosmos.shadeRepository
+ shadeTestUtil = kosmos.shadeTestUtil
keyguardRepository = kosmos.fakeKeyguardRepository
underTest = kosmos.lockscreenToDreamingTransitionViewModel
}
@@ -177,11 +193,11 @@
private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
} else {
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- shadeRepository.setQsExpansion(0f)
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index b120f87..f8da74f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,27 +18,28 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
@@ -48,25 +49,40 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
private lateinit var repository: FakeKeyguardTransitionRepository
- private lateinit var shadeRepository: ShadeRepository
+ private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var configurationRepository: FakeConfigurationRepository
private lateinit var underTest: LockscreenToOccludedTransitionViewModel
+ // add to init block
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
repository = kosmos.fakeKeyguardTransitionRepository
- shadeRepository = kosmos.shadeRepository
+ shadeTestUtil = kosmos.shadeTestUtil
keyguardRepository = kosmos.fakeKeyguardRepository
configurationRepository = kosmos.fakeConfigurationRepository
underTest = kosmos.lockscreenToOccludedTransitionViewModel
@@ -200,11 +216,11 @@
private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
} else {
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- shadeRepository.setQsExpansion(0f)
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 43ab93a..d5df159 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,11 +16,12 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -29,29 +30,50 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) :
+ SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
private val repository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+ private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel
+ }
@Test
fun deviceEntryParentViewAlpha_shadeExpanded() =
@@ -119,11 +141,11 @@
private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setQsExpansion(1f)
} else {
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- shadeRepository.setQsExpansion(0f)
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
new file mode 100644
index 0000000..0761ee7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.oneHandedModeRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.wm.shell.onehanded.OneHanded
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileDataInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val testUser = UserHandle.of(1)!!
+ private val oneHandedModeRepository = kosmos.oneHandedModeRepository
+ private val underTest: OneHandedModeTileDataInteractor =
+ OneHandedModeTileDataInteractor(oneHandedModeRepository)
+
+ @Test
+ fun availability_matchesController() = runTest {
+ val expectedAvailability = OneHanded.sIsSupportOneHandedMode
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun data_matchesRepository() = runTest {
+ val lastData by
+ collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+ runCurrent()
+ assertThat(lastData!!.isEnabled).isFalse()
+
+ oneHandedModeRepository.setIsEnabled(true, testUser)
+ runCurrent()
+ assertThat(lastData!!.isEnabled).isTrue()
+
+ oneHandedModeRepository.setIsEnabled(false, testUser)
+ runCurrent()
+ assertThat(lastData!!.isEnabled).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..3f17d4c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeOneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val testUser = UserHandle.of(1)
+ private val repository = FakeOneHandedModeRepository()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+ private val underTest =
+ OneHandedModeTileUserActionInteractor(
+ repository,
+ inputHandler,
+ )
+
+ @Test
+ fun handleClickWhenEnabled() = runTest {
+ val wasEnabled = true
+ repository.setIsEnabled(wasEnabled, testUser)
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+ )
+
+ assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleClickWhenDisabled() = runTest {
+ val wasEnabled = false
+ repository.setIsEnabled(wasEnabled, testUser)
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser)
+ )
+
+ assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() = runTest {
+ val enabled = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() = runTest {
+ val enabled = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser)
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
new file mode 100644
index 0000000..7ef020d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tileimpl.SubtitleArrayMapping
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.qsOneHandedModeTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OneHandedModeTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsOneHandedModeTileConfig
+ private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(config.tileSpec.spec)
+ private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) }
+
+ private lateinit var mapper: OneHandedModeTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ OneHandedModeTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
+ TestStubDrawable()
+ )
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun disabledModel() {
+ val inputModel = OneHandedModeTileModel(false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createOneHandedModeTileState(
+ QSTileState.ActivationState.INACTIVE,
+ subtitleArray[1],
+ com.android.internal.R.drawable.ic_qs_one_handed_mode
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel() {
+ val inputModel = OneHandedModeTileModel(true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createOneHandedModeTileState(
+ QSTileState.ActivationState.ACTIVE,
+ subtitleArray[2],
+ com.android.internal.R.drawable.ic_qs_one_handed_mode
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createOneHandedModeTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_onehanded_label)
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 78b7615..cbbc4d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,116 +18,92 @@
import android.graphics.Rect
import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
-import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository
import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
-import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
-import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) :
+ SysuiTestCase() {
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<NotificationIconContainerStatusBarViewModel> {
-
- val activeNotificationsRepository: ActiveNotificationListRepository
- val darkIconRepository: FakeDarkIconRepository
- val deviceProvisioningRepository: FakeDeviceProvisioningRepository
- val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
- val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- val keyguardRepository: FakeKeyguardRepository
- val powerRepository: FakePowerRepository
- val shadeRepository: FakeShadeRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
}
}
- private val dozeParams: DozeParameters = mock()
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
- private val testComponent: TestComponent =
- DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- ),
- )
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerRepository = kosmos.fakePowerRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val darkIconRepository = kosmos.fakeDarkIconRepository
+ private val headsUpViewStateRepository = kosmos.headsUpNotificationIconViewStateRepository
+ private val activeNotificationsRepository = kosmos.activeNotificationListRepository
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private val dozeParams = kosmos.dozeParameters
+
+ lateinit var underTest: NotificationIconContainerStatusBarViewModel
@Before
fun setup() {
- testComponent.apply {
- keyguardRepository.setKeyguardShowing(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- }
+ underTest = kosmos.notificationIconContainerStatusBarViewModel
+ keyguardRepository.setKeyguardShowing(false)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -150,7 +126,7 @@
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -173,7 +149,7 @@
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -194,7 +170,7 @@
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- testComponent.runTest {
+ testScope.runTest {
val animationsEnabled by collectLastValue(underTest.animationsEnabled)
assertThat(animationsEnabled).isTrue()
@@ -218,7 +194,7 @@
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- testComponent.runTest {
+ testScope.runTest {
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -236,7 +212,7 @@
@Test
fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
- testComponent.runTest {
+ testScope.runTest {
val animationsEnabled by collectLastValue(underTest.animationsEnabled)
keyguardTransitionRepository.sendTransitionStep(
@@ -257,7 +233,7 @@
@Test
fun iconColors_testsDarkBounds() =
- testComponent.runTest {
+ testScope.runTest {
darkIconRepository.darkState.value =
SysuiDarkIconDispatcher.DarkChange(
emptyList(),
@@ -280,7 +256,7 @@
@Test
fun iconColors_staticDrawableColor_notInDarkTintArea() =
- testComponent.runTest {
+ testScope.runTest {
darkIconRepository.darkState.value =
SysuiDarkIconDispatcher.DarkChange(
listOf(Rect(0, 0, 5, 5)),
@@ -295,7 +271,7 @@
@Test
fun iconColors_notInDarkTintArea() =
- testComponent.runTest {
+ testScope.runTest {
darkIconRepository.darkState.value =
SysuiDarkIconDispatcher.DarkChange(
listOf(Rect(0, 0, 5, 5)),
@@ -309,9 +285,9 @@
@Test
fun isolatedIcon_animateOnAppear_shadeCollapsed() =
- testComponent.runTest {
+ testScope.runTest {
val icon: Icon = mock()
- shadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
activeNotificationsRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply {
@@ -336,9 +312,9 @@
@Test
fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
- testComponent.runTest {
+ testScope.runTest {
val icon: Icon = mock()
- shadeRepository.setLegacyShadeExpansion(.5f)
+ shadeTestUtil.setShadeExpansion(.5f)
activeNotificationsRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
.apply {
@@ -363,7 +339,7 @@
@Test
fun isolatedIcon_updateWhenIconDataChanges() =
- testComponent.runTest {
+ testScope.runTest {
val icon: Icon = mock()
val isolatedIcon by collectLastValue(underTest.isolatedIcon)
runCurrent()
@@ -390,7 +366,7 @@
@Test
fun isolatedIcon_lastMessageIsFromReply_notNull() =
- testComponent.runTest {
+ testScope.runTest {
val icon: Icon = mock()
headsUpViewStateRepository.isolatedNotification.value = "notif1"
activeNotificationsRepository.activeNotifications.value =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
index dc96139..dddf582 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -61,6 +61,7 @@
AncSliceRepositoryImpl(
localMediaRepositoryFactory,
testScope.testScheduler,
+ testScope.testScheduler,
sliceViewManager,
)
}
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 2769bea..73812c9 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -39,7 +39,7 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">6dp</item>
<item name="android:textSize">36dp</item>
<item name="android:focusable">true</item>
@@ -47,14 +47,14 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">6dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Description">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">6dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
index 0d46cbc..cde1a1373 100644
--- a/packages/SystemUI/res/values-sw600dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -18,7 +18,7 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">36sp</item>
<item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Description">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw600dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -26,7 +26,7 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">24dp</item>
<item name="android:textSize">36sp</item>
<item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
index 7cdd07b..e75173d 100644
--- a/packages/SystemUI/res/values-sw720dp-land/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -18,7 +18,7 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">36sp</item>
<item name="android:focusable">true</item>
@@ -26,14 +26,14 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Description">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">16dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
index 3add566..85e7af6 100644
--- a/packages/SystemUI/res/values-sw720dp-port/styles.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -26,7 +26,7 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">24dp</item>
<item name="android:textSize">36sp</item>
<item name="android:focusable">true</item>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 69de45e..2c4cdb9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -175,21 +175,21 @@
</style>
<style name="TextAppearance.AuthCredential.OldTitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">24sp</item>
</style>
<style name="TextAppearance.AuthCredential.OldSubtitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.AuthCredential.OldDescription">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item>
@@ -205,7 +205,7 @@
</style>
<style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" >
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
@@ -257,7 +257,7 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Title">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">24dp</item>
<item name="android:textSize">36dp</item>
<item name="android:focusable">true</item>
@@ -265,14 +265,14 @@
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">20dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Description">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:layout_marginTop">20dp</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c08b083..69aa909 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -77,7 +77,7 @@
// settings is expanded.
public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
// Winscope tracing is enabled
- public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12;
+ public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
// The Assistant gesture should be constrained. It is up to the launcher implementation to
// decide how to constrain it
public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
@@ -148,7 +148,7 @@
SYSUI_STATE_OVERVIEW_DISABLED,
SYSUI_STATE_HOME_DISABLED,
SYSUI_STATE_SEARCH_DISABLED,
- SYSUI_STATE_TRACING_ENABLED,
+ SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
SYSUI_STATE_BUBBLES_EXPANDED,
SYSUI_STATE_DIALOG_SHOWING,
@@ -211,8 +211,8 @@
if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) {
str.add("a11y_long_click");
}
- if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) {
- str.add("tracing");
+ if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) {
+ str.add("disable_gesture_split_invocation");
}
if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) {
str.add("asst_gesture_constrain");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a90b5..91fb688 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -35,14 +35,12 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.media.AudioManager;
import android.metrics.LogMaker;
-import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -98,15 +96,12 @@
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
-import com.google.common.util.concurrent.ListenableFuture;
-
import dagger.Lazy;
import kotlinx.coroutines.Job;
import java.io.File;
import java.util.Arrays;
-import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -139,7 +134,6 @@
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
- private final DevicePolicyManager mDevicePolicyManager;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
// the audio service will bring up the volume dialog.
@@ -466,7 +460,6 @@
SelectedUserInteractor selectedUserInteractor,
DeviceProvisionedController deviceProvisionedController,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
- DevicePolicyManager devicePolicyManager,
KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
Provider<DeviceEntryInteractor> deviceEntryInteractor
@@ -502,7 +495,6 @@
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mDeviceProvisionedController = deviceProvisionedController;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
- mDevicePolicyManager = devicePolicyManager;
}
@Override
@@ -1113,36 +1105,35 @@
if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+ final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
final int failedAttemptsBeforeWipe =
- mDevicePolicyManager.getMaximumFailedPasswordsForWipe(null, userId);
+ dpm.getMaximumFailedPasswordsForWipe(null, userId);
final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
? (failedAttemptsBeforeWipe - failedAttempts)
: Integer.MAX_VALUE; // because DPM returns 0 if no restriction
if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
- // The user has installed a DevicePolicyManager that requests a
- // user/profile to be wiped N attempts. Once we get below the grace period,
- // we post this dialog every time as a clear warning until the deletion
- // fires. Check which profile has the strictest policy for failed password
- // attempts.
- final int expiringUser =
- mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId);
- ListenableFuture<Integer> getMainUserIdFuture =
- mSelectedUserInteractor.getMainUserIdAsync();
- getMainUserIdFuture.addListener(() -> {
- Looper.prepare();
- Integer mainUser;
- try {
- mainUser = getMainUserIdFuture.get();
- } catch (InterruptedException | ExecutionException e) {
- // Nothing we can, keep using the system user as the primary
- // user.
- mainUser = null;
+ // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+ // N attempts. Once we get below the grace period, we post this dialog every time as a
+ // clear warning until the deletion fires.
+ // Check which profile has the strictest policy for failed password attempts
+ final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
+ int userType = USER_TYPE_PRIMARY;
+ if (expiringUser == userId) {
+ // TODO: http://b/23522538
+ if (expiringUser != UserHandle.USER_SYSTEM) {
+ userType = USER_TYPE_SECONDARY_USER;
}
- showMessageForFailedUnlockAttempt(
- userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts);
- Looper.loop();
- }, ThreadUtils.getBackgroundExecutor());
+ } else if (expiringUser != UserHandle.USER_NULL) {
+ userType = USER_TYPE_WORK_PROFILE;
+ } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+ if (remainingBeforeWipe > 0) {
+ mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
+ } else {
+ // Too many attempts. The device will be wiped shortly.
+ Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
+ mView.showWipeDialog(failedAttempts, userType);
+ }
}
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
@@ -1154,35 +1145,6 @@
}
}
- @VisibleForTesting
- void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId,
- int remainingBeforeWipe, int failedAttempts) {
- int userType = USER_TYPE_PRIMARY;
- if (expiringUserId == userId) {
- int primaryUser = UserHandle.USER_SYSTEM;
- if (Flags.headlessSingleUserFixes()) {
- if (mainUserId != null) {
- primaryUser = mainUserId;
- }
- }
- // TODO: http://b/23522538
- if (expiringUserId != primaryUser) {
- userType = USER_TYPE_SECONDARY_USER;
- }
- } else if (expiringUserId != UserHandle.USER_NULL) {
- userType = USER_TYPE_WORK_PROFILE;
- } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
- if (remainingBeforeWipe > 0) {
- mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe,
- userType);
- } else {
- // Too many attempts. The device will be wiped shortly.
- Slog.i(TAG, "Too many unlock attempts; user " + expiringUserId
- + " will be wiped!");
- mView.showWipeDialog(failedAttempts, userType);
- }
- }
-
private void getCurrentSecurityController(
KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) {
mSecurityViewFlipperController
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 35f9344..004d5db 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -22,6 +22,8 @@
import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
import com.android.systemui.accessibility.data.repository.ColorInversionRepository
import com.android.systemui.accessibility.data.repository.ColorInversionRepositoryImpl
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepositoryImpl
import com.android.systemui.accessibility.qs.QSAccessibilityModule
import dagger.Binds
import dagger.Module
@@ -34,6 +36,8 @@
@Binds
fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+ @Binds fun oneHandedModeRepository(impl: OneHandedModeRepositoryImpl): OneHandedModeRepository
+
@Binds
fun accessibilityQsShortcutsRepository(
impl: AccessibilityQsShortcutsRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
new file mode 100644
index 0000000..d921025
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Provides data related to one handed mode. */
+interface OneHandedModeRepository {
+ /** Observable for whether one handed mode is enabled */
+ fun isEnabled(userHandle: UserHandle): Flow<Boolean>
+
+ /** Sets one handed mode enabled state. */
+ suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean
+}
+
+@SysUISingleton
+class OneHandedModeRepositoryImpl
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val secureSettings: SecureSettings,
+) : OneHandedModeRepository {
+
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
+ override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) ==
+ ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE)
+ }
+
+ override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
+ withContext(bgCoroutineContext) {
+ secureSettings.putIntForUser(
+ SETTING_NAME,
+ if (isEnabled) ENABLED else DISABLED,
+ userHandle.identifier
+ )
+ }
+
+ companion object {
+ private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ private const val DEFAULT_VALUE = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 99be762..54dd6d0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,10 @@
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.impl.onehanded.ui.OneHandedModeTileMapper
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
@@ -256,5 +260,24 @@
),
instanceId = uiEventLogger.getNewInstanceId(),
)
+
+ /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */
+ @Provides
+ @IntoMap
+ @StringKey(ONE_HANDED_TILE_SPEC)
+ fun provideOneHandedModeTileViewModel(
+ factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>,
+ mapper: OneHandedModeTileMapper,
+ stateInteractor: OneHandedModeTileDataInteractor,
+ userActionInteractor: OneHandedModeTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(ONE_HANDED_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index fcba425..5df7fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.authentication.domain.interactor
-import android.app.admin.flags.Flags
import android.os.UserHandle
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
@@ -289,15 +288,9 @@
private suspend fun getWipeTarget(): WipeTarget {
// Check which profile has the strictest policy for failed authentication attempts.
val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe()
- val primaryUser =
- if (Flags.headlessSingleUserFixes()) {
- selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
- } else {
- UserHandle.USER_SYSTEM
- }
return when (userToBeWiped) {
selectedUserInteractor.getSelectedUserId() ->
- if (userToBeWiped == primaryUser) {
+ if (userToBeWiped == UserHandle.USER_SYSTEM) {
WipeTarget.WholeDevice
} else {
WipeTarget.User
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index a8e9041..0f63f65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -41,6 +41,7 @@
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
@@ -196,6 +197,12 @@
@Binds
@IntoSet
+ abstract fun offToLockscreen(
+ impl: OffToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun primaryBouncerToAod(
impl: PrimaryBouncerToAodTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 40be73e..da2fcc4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -84,19 +84,21 @@
.map { it.deviceEntryParentViewAlpha }
.merge()
.shareIn(scope, SharingStarted.WhileSubscribed())
+ .onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) }
private val alphaMultiplierFromShadeExpansion: Flow<Float> =
combine(
- showingAlternateBouncer,
- shadeExpansion,
- qsProgress,
- ) { showingAltBouncer, shadeExpansion, qsProgress ->
- val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
- if (showingAltBouncer) {
- 1f
- } else {
- (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+ showingAlternateBouncer,
+ shadeExpansion,
+ qsProgress,
+ ) { showingAltBouncer, shadeExpansion, qsProgress ->
+ val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f)
+ if (showingAltBouncer) {
+ 1f
+ } else {
+ (1f - shadeExpansion) * (1f - interpolatedQsProgress)
+ }
}
- }
+ .onStart { emit(1f) }
// Burn-in offsets in AOD
private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> =
combine(
@@ -122,14 +124,34 @@
)
}
- val deviceEntryViewAlpha: StateFlow<Float> =
+ val deviceEntryViewAlpha: Flow<Float> =
combine(
transitionAlpha,
alphaMultiplierFromShadeExpansion,
) { alpha, alphaMultiplier ->
alpha * alphaMultiplier
}
- .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f)
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 0f,
+ )
+
+ private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float {
+ return when (keyguardState) {
+ KeyguardState.OFF,
+ KeyguardState.PRIMARY_BOUNCER,
+ KeyguardState.DOZING,
+ KeyguardState.DREAMING,
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.GONE,
+ KeyguardState.OCCLUDED,
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f
+ KeyguardState.AOD,
+ KeyguardState.ALTERNATE_BOUNCER,
+ KeyguardState.LOCKSCREEN -> 1f
+ }
+ }
val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
val burnInOffsets: Flow<BurnInOffsets> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 74094be..cf6a533 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -19,6 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
@@ -28,7 +29,7 @@
@Inject
constructor(
animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
@@ -43,4 +44,7 @@
onStep = { it },
onCancel = { 0f },
)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
new file mode 100644
index 0000000..8c0fd2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.domain
+
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.wm.shell.onehanded.OneHanded
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes one handed mode state changes providing the [OneHandedModeTileModel]. */
+class OneHandedModeTileDataInteractor
+@Inject
+constructor(
+ private val oneHandedModeRepository: OneHandedModeRepository,
+) : QSTileDataInteractor<OneHandedModeTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<OneHandedModeTileModel> {
+ return oneHandedModeRepository.isEnabled(user).map { OneHandedModeTileModel(it) }
+ }
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(OneHanded.sIsSupportOneHandedMode)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cb0e18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.domain
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.OneHandedModeRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles one handed mode tile clicks. */
+class OneHandedModeTileUserActionInteractor
+@Inject
+constructor(
+ private val oneHandedModeRepository: OneHandedModeRepository,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<OneHandedModeTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<OneHandedModeTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ oneHandedModeRepository.setIsEnabled(
+ !data.isEnabled,
+ user,
+ )
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ Intent(Settings.ACTION_ONE_HANDED_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
new file mode 100644
index 0000000..7cebdfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.domain.model
+
+/**
+ * One handed mode tile model.
+ *
+ * @param isEnabled is true when one handed mode is enabled;
+ */
+@JvmInline value class OneHandedModeTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
new file mode 100644
index 0000000..9166ed8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [OneHandedModeTileModel] to [QSTileState]. */
+class OneHandedModeTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+
+ override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
+ label = resources.getString(R.string.quick_settings_onehanded_label)
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
+ theme
+ ),
+ null
+ )
+ }
+ if (data.isEnabled) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = subtitleArray[2]
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = subtitleArray[1]
+ }
+ sideViewIcon = QSTileState.SideViewIcon.None
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 0673dcd..76bd80f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -37,7 +37,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
@@ -118,8 +117,6 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -131,6 +128,8 @@
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -701,8 +700,7 @@
// Listen for tracing state changes
@Override
public void onTracingStateChanged(boolean enabled) {
- mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
- .commitUpdate(mContext.getDisplayId());
+ // TODO(b/286509643) Cleanup callers of this; Unused downstream
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 07e143a..ef1d87d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -87,7 +87,8 @@
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
context.resources.getString(R.string.screenshot_edit_label),
context.resources.getString(R.string.screenshot_edit_description),
- )
+ ),
+ showDuringEntrance = true,
) {
debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
@@ -105,7 +106,8 @@
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
context.resources.getString(R.string.screenshot_share_description),
- )
+ ),
+ showDuringEntrance = true,
) {
debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
@@ -125,7 +127,8 @@
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
context.resources.getString(R.string.screenshot_scroll_label),
context.resources.getString(R.string.screenshot_scroll_label),
- )
+ ),
+ showDuringEntrance = true,
) {
onClick.run()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 9b5e7182..412b089 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -45,6 +45,7 @@
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -119,12 +120,19 @@
override fun updateOrientation(insets: WindowInsets) {}
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
- val entrance = animationController.getEntranceAnimation(screenRect, showFlash)
- entrance.doOnStart { thumbnailObserver.onEntranceStarted() }
+ val entrance =
+ animationController.getEntranceAnimation(screenRect, showFlash) {
+ viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
+ }
+ entrance.doOnStart {
+ thumbnailObserver.onEntranceStarted()
+ viewModel.setAnimationState(AnimationState.ENTRANCE_STARTED)
+ }
entrance.doOnEnd {
// reset the timeout when animation finishes
callbacks?.onUserInteraction()
thumbnailObserver.onEntranceComplete()
+ viewModel.setAnimationState(AnimationState.ENTRANCE_COMPLETE)
}
return entrance
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
index da26830..06e88f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt
@@ -47,7 +47,11 @@
view.requireViewById(R.id.screenshot_dismiss_button)
)
- fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator {
+ fun getEntranceAnimation(
+ bounds: Rect,
+ showFlash: Boolean,
+ onRevealMilestone: () -> Unit
+ ): Animator {
val entranceAnimation = AnimatorSet()
val previewAnimator = getPreviewAnimator(bounds)
@@ -70,7 +74,19 @@
entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE }
}
- entranceAnimation.play(getActionsAnimator()).with(previewAnimator)
+ val actionsAnimator = getActionsAnimator()
+ entranceAnimation.play(actionsAnimator).with(previewAnimator)
+
+ // This isn't actually animating anything but is basically a timer for the first 200ms of
+ // the entrance animation. Using an animator here ensures that this is scaled if we change
+ // animator duration scales.
+ val revealMilestoneAnimator =
+ ValueAnimator.ofFloat(0f).apply {
+ duration = 0
+ startDelay = ACTION_REVEAL_DELAY_MS
+ doOnEnd { onRevealMilestone() }
+ }
+ entranceAnimation.play(revealMilestoneAnimator).with(actionsAnimator)
val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f)
fadeInAnimator.addUpdateListener {
@@ -198,5 +214,6 @@
private const val FLASH_OUT_DURATION_MS: Long = 217
private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234
private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500
+ private const val ACTION_REVEAL_DELAY_MS: Long = 200
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index bc35e6b..43c0107 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -31,6 +31,8 @@
import com.android.systemui.screenshot.ScreenshotEvent
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.SwipeGestureListener
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.AnimationState
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.children
import kotlinx.coroutines.Dispatchers
@@ -59,7 +61,6 @@
val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border)
previewView.clipToOutline = true
previewViewBlur.clipToOutline = true
- val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button)
dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE
dismissButton.setOnClickListener {
@@ -90,44 +91,22 @@
}
launch {
viewModel.actions.collect { actions ->
- val visibleActions = actions.filter { it.visible }
-
- if (visibleActions.isNotEmpty()) {
- view
- .requireViewById<View>(R.id.actions_container_background)
- .visibility = View.VISIBLE
- }
-
- // Remove any buttons not in the new list, then do another pass to add
- // any new actions and update any that are already there.
- // This assumes that actions can never change order and that each action
- // ID is unique.
- val newIds = visibleActions.map { it.id }
-
- for (child in actionsContainer.children.toList()) {
- if (child.tag !in newIds) {
- actionsContainer.removeView(child)
- }
- }
-
- for ((index, action) in visibleActions.withIndex()) {
- val currentView: View? = actionsContainer.getChildAt(index)
- if (action.id == currentView?.tag) {
- // Same ID, update the display
- ActionButtonViewBinder.bind(currentView, action)
- } else {
- // Different ID. Removals have already happened so this must
- // mean that the new action must be inserted here.
- val actionButton =
- layoutInflater.inflate(
- R.layout.shelf_action_chip,
- actionsContainer,
- false
- )
- actionsContainer.addView(actionButton, index)
- ActionButtonViewBinder.bind(actionButton, action)
- }
- }
+ updateActions(
+ actions,
+ viewModel.animationState.value,
+ view,
+ layoutInflater
+ )
+ }
+ }
+ launch {
+ viewModel.animationState.collect { animationState ->
+ updateActions(
+ viewModel.actions.value,
+ animationState,
+ view,
+ layoutInflater
+ )
}
}
}
@@ -135,6 +114,53 @@
}
}
+ private fun updateActions(
+ actions: List<ActionButtonViewModel>,
+ animationState: AnimationState,
+ view: ScreenshotShelfView,
+ layoutInflater: LayoutInflater
+ ) {
+ val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions)
+ val visibleActions =
+ actions.filter {
+ it.visible &&
+ (animationState == AnimationState.ENTRANCE_COMPLETE ||
+ animationState == AnimationState.ENTRANCE_REVEAL ||
+ it.showDuringEntrance)
+ }
+
+ if (visibleActions.isNotEmpty()) {
+ view.requireViewById<View>(R.id.actions_container_background).visibility = View.VISIBLE
+ }
+
+ // Remove any buttons not in the new list, then do another pass to add
+ // any new actions and update any that are already there.
+ // This assumes that actions can never change order and that each action
+ // ID is unique.
+ val newIds = visibleActions.map { it.id }
+
+ for (child in actionsContainer.children.toList()) {
+ if (child.tag !in newIds) {
+ actionsContainer.removeView(child)
+ }
+ }
+
+ for ((index, action) in visibleActions.withIndex()) {
+ val currentView: View? = actionsContainer.getChildAt(index)
+ if (action.id == currentView?.tag) {
+ // Same ID, update the display
+ ActionButtonViewBinder.bind(currentView, action)
+ } else {
+ // Different ID. Removals have already happened so this must
+ // mean that the new action must be inserted here.
+ val actionButton =
+ layoutInflater.inflate(R.layout.shelf_action_chip, actionsContainer, false)
+ actionsContainer.addView(actionButton, index)
+ ActionButtonViewBinder.bind(actionButton, action)
+ }
+ }
+ }
+
private fun setScreenshotBitmap(screenshotPreview: ImageView, bitmap: Bitmap) {
screenshotPreview.setImageBitmap(bitmap)
val hasPortraitAspectRatio = bitmap.width < bitmap.height
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index c5fa8db..364ab76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -20,6 +20,7 @@
val appearance: ActionButtonAppearance,
val id: Int,
val visible: Boolean,
+ val showDuringEntrance: Boolean,
val onClicked: (() -> Unit)?,
) {
companion object {
@@ -29,7 +30,14 @@
fun withNextId(
appearance: ActionButtonAppearance,
+ showDuringEntrance: Boolean,
onClicked: (() -> Unit)?
- ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked)
+ ): ActionButtonViewModel =
+ ActionButtonViewModel(appearance, getId(), true, showDuringEntrance, onClicked)
+
+ fun withNextId(
+ appearance: ActionButtonAppearance,
+ onClicked: (() -> Unit)?
+ ): ActionButtonViewModel = withNextId(appearance, showDuringEntrance = true, onClicked)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index f67ad40..5f36f73 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -29,6 +29,9 @@
val previewAction: StateFlow<(() -> Unit)?> = _previewAction
private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
val actions: StateFlow<List<ActionButtonViewModel>> = _actions
+ private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED)
+ val animationState: StateFlow<AnimationState> = _animationState
+
val showDismissButton: Boolean
get() = accessibilityManager.isEnabled
@@ -40,9 +43,14 @@
_previewAction.value = onClick
}
- fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
+ fun addAction(
+ actionAppearance: ActionButtonAppearance,
+ showDuringEntrance: Boolean,
+ onClicked: (() -> Unit)
+ ): Int {
val actionList = _actions.value.toMutableList()
- val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
+ val action =
+ ActionButtonViewModel.withNextId(actionAppearance, showDuringEntrance, onClicked)
actionList.add(action)
_actions.value = actionList
return action.id
@@ -57,6 +65,7 @@
actionList[index].appearance,
actionId,
visible,
+ actionList[index].showDuringEntrance,
actionList[index].onClicked
)
_actions.value = actionList
@@ -74,6 +83,7 @@
appearance,
actionId,
actionList[index].visible,
+ actionList[index].showDuringEntrance,
actionList[index].onClicked
)
_actions.value = actionList
@@ -92,13 +102,26 @@
}
}
+ // TODO: this should be handled entirely within the view binder.
+ fun setAnimationState(state: AnimationState) {
+ _animationState.value = state
+ }
+
fun reset() {
_preview.value = null
_previewAction.value = null
_actions.value = listOf()
+ _animationState.value = AnimationState.NOT_STARTED
}
companion object {
const val TAG = "ScreenshotViewModel"
}
}
+
+enum class AnimationState {
+ NOT_STARTED,
+ ENTRANCE_STARTED, // The first 200ms of the entrance animation
+ ENTRANCE_REVEAL, // The rest of the entrance animation
+ ENTRANCE_COMPLETE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
index 384acc4..dd79425 100644
--- a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
@@ -28,6 +28,9 @@
* Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
* provided Uri. This can change overtime because of external changes (like device being
* connected/disconnected).
+ *
+ * The flow should be [kotlinx.coroutines.flow.flowOn] the main thread because [SliceViewManager]
+ * isn't thread-safe. An exception will be thrown otherwise.
*/
fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
ConflatedCallbackFlow.conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e980794..d0cebae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -169,6 +169,14 @@
}
}
+ // On the final call to {@link #resetViewState}, the alpha is set back to 1f but
+ // ambientState.isExpansionChanging() is now false. This causes a flicker on the
+ // EmptyShadeView after the shade is collapsed. Make sure the empty shade view
+ // isn't visible unless the shade is expanded.
+ if (view instanceof EmptyShadeView && ambientState.getExpansionFraction() == 0f) {
+ viewState.setAlpha(0f);
+ }
+
// For EmptyShadeView if on keyguard, we need to control the alpha to create
// a nice transition when the user is dragging down the notification panel.
if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index dea9416..2e1ab38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -59,7 +59,10 @@
}
override fun notifyThemeChanged() {
- val listeners = ArrayList(listeners)
+ // Avoid concurrent modification exception
+ val listeners = synchronized(this.listeners) {
+ ArrayList(this.listeners)
+ }
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onThemeChanged()
@@ -68,8 +71,9 @@
override fun onConfigurationChanged(newConfig: Configuration) {
// Avoid concurrent modification exception
- val listeners = ArrayList(listeners)
-
+ val listeners = synchronized(this.listeners) {
+ ArrayList(this.listeners)
+ }
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onConfigChanged(newConfig)
}
@@ -148,12 +152,16 @@
}
override fun addCallback(listener: ConfigurationListener) {
- listeners.add(listener)
+ synchronized(listeners) {
+ listeners.add(listener)
+ }
listener.onDensityOrFontScaleChanged()
}
override fun removeCallback(listener: ConfigurationListener) {
- listeners.remove(listener)
+ synchronized(listeners) {
+ listeners.remove(listener)
+ }
}
override fun isLayoutRtl(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 0739b836..a7c4187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -318,8 +318,8 @@
}
companion object {
- // TTL for satellite polling is one hour
- const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
+ // TTL for satellite polling is twenty minutes
+ const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 20
// Let the system boot up and stabilize before we check for system support
const val MIN_UPTIME: Long = 1000 * 60
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index a817b31..37be1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -18,7 +18,6 @@
package com.android.systemui.user.data.repository
import android.annotation.SuppressLint
-import android.annotation.UserIdInt
import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -108,22 +107,6 @@
fun isSimpleUserSwitcher(): Boolean
fun isUserSwitcherEnabled(): Boolean
-
- /**
- * Returns the user ID of the "main user" of the device. This user may have access to certain
- * features which are limited to at most one user. There will never be more than one main user
- * on a device.
- *
- * <p>Currently, on most form factors the first human user on the device will be the main user;
- * in the future, the concept may be transferable, so a different user (or even no user at all)
- * may be designated the main user instead. On other form factors there might not be a main
- * user.
- *
- * <p> When the device doesn't have a main user, this will return {@code null}.
- *
- * @see [UserManager.getMainUser]
- */
- @UserIdInt suspend fun getMainUserId(): Int?
}
@SysUISingleton
@@ -256,10 +239,6 @@
return _userSwitcherSettings.value.isUserSwitcherEnabled
}
- override suspend fun getMainUserId(): Int? {
- return withContext(backgroundDispatcher) { manager.mainUser?.identifier }
- }
-
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index a5728d0..38b381a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -2,27 +2,17 @@
import android.annotation.UserIdInt
import android.content.pm.UserInfo
-import android.os.UserManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags.refactorGetCurrentUser
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.user.data.repository.UserRepository
-import com.google.common.util.concurrent.ListenableFuture
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.guava.future
/** Encapsulates business logic to interact the selected user */
@SysUISingleton
-class SelectedUserInteractor
-@Inject
-constructor(
- @Application private val applicationScope: CoroutineScope,
- private val repository: UserRepository
-) {
+class SelectedUserInteractor @Inject constructor(private val repository: UserRepository) {
/** Flow providing the ID of the currently selected user. */
val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged()
@@ -48,41 +38,4 @@
KeyguardUpdateMonitor.getCurrentUser()
}
}
-
- /**
- * Returns the user ID of the "main user" of the device. This user may have access to certain
- * features which are limited to at most one user. There will never be more than one main user
- * on a device.
- *
- * <p>Currently, on most form factors the first human user on the device will be the main user;
- * in the future, the concept may be transferable, so a different user (or even no user at all)
- * may be designated the main user instead. On other form factors there might not be a main
- * user.
- *
- * <p> When the device doesn't have a main user, this will return {@code null}.
- *
- * @see [UserManager.getMainUser]
- */
- @UserIdInt
- suspend fun getMainUserId(): Int? {
- return repository.getMainUserId()
- }
-
- /**
- * Returns a [ListenableFuture] for the user ID of the "main user" of the device. This user may
- * have access to certain features which are limited to at most one user. There will never be
- * more than one main user on a device.
- *
- * <p>Currently, on most form factors the first human user on the device will be the main user;
- * in the future, the concept may be transferable, so a different user (or even no user at all)
- * may be designated the main user instead. On other form factors there might not be a main
- * user.
- *
- * <p> When the device doesn't have a main user, this will return {@code null}.
- *
- * @see [UserManager.getMainUser]
- */
- fun getMainUserIdAsync(): ListenableFuture<Int?> {
- return applicationScope.future { getMainUserId() }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
index 8ce3b1f..3117abc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.slice.sliceForUri
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import dagger.assisted.Assisted
@@ -57,6 +58,7 @@
constructor(
mediaRepositoryFactory: LocalMediaRepositoryFactory,
@Background private val backgroundCoroutineContext: CoroutineContext,
+ @Main private val mainCoroutineContext: CoroutineContext,
@Assisted private val sliceViewManager: SliceViewManager,
) : AncSliceRepository {
@@ -73,7 +75,7 @@
.distinctUntilChanged()
.flatMapLatest { sliceUri ->
sliceUri ?: return@flatMapLatest flowOf(null)
- sliceViewManager.sliceForUri(sliceUri)
+ sliceViewManager.sliceForUri(sliceUri).flowOn(mainCoroutineContext)
}
.flowOn(backgroundCoroutineContext)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
index bee79bb..c980eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -16,7 +16,9 @@
package com.android.systemui.volume.panel.component.anc.ui.viewmodel
+import android.content.Intent
import androidx.slice.Slice
+import androidx.slice.SliceItem
import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices
@@ -59,6 +61,31 @@
.map { it.buttonSlice }
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ fun isClickable(slice: Slice?): Boolean {
+ slice ?: return false
+ val slices = ArrayDeque<SliceItem>()
+ slices.addAll(slice.items)
+ while (slices.isNotEmpty()) {
+ val item: SliceItem = slices.removeFirst()
+ when (item.format) {
+ android.app.slice.SliceItem.FORMAT_ACTION -> {
+ val itemActionIntent: Intent? = item.action?.intent
+ if (itemActionIntent?.hasExtra(EXTRA_ANC_ENABLED) == true) {
+ return itemActionIntent.getBooleanExtra(EXTRA_ANC_ENABLED, true)
+ }
+ }
+ android.app.slice.SliceItem.FORMAT_SLICE -> {
+ item.slice?.items?.let(slices::addAll)
+ }
+ }
+ }
+ return true
+ }
+
+ private companion object {
+ const val EXTRA_ANC_ENABLED = "EXTRA_ANC_ENABLED"
+ }
+
/** Call this to update [popupSlice] width in a reaction to container size change. */
fun onPopupSliceWidthChanged(width: Int) {
interactor.onPopupSliceWidthChanged(width)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 263ddc1..b86a7c9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,6 +21,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -273,6 +274,13 @@
splitScreen.setSplitscreenFocus(leftOrTop);
}
});
+ splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() {
+ @Override
+ public void onSplitAnimationInvoked(boolean animationRunning) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+ }, mSysUiMainExecutor);
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 67ca9a4..0231486 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -16,80 +16,73 @@
package com.android.systemui.biometrics
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.verifyZeroInteractions
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() {
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- UserDomainLayerModule::class,
- BiometricsDomainLayerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> {
-
- val shadeRepository: FakeShadeRepository
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
}
}
- private val testComponent: TestComponent =
- DaggerAuthDialogPanelInteractionDetectorTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
- )
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
- private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
@Mock private lateinit var action: Runnable
+ lateinit var detector: AuthDialogPanelInteractionDetector
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ detector =
+ AuthDialogPanelInteractionDetector(
+ kosmos.applicationCoroutineScope,
+ { kosmos.shadeInteractor },
+ )
}
@Test
fun enableDetector_expand_shouldRunAction() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN shade is closed and detector is enabled
- shadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
detector.enable(action)
runCurrent()
// WHEN shade expands
- shadeRepository.setLegacyShadeTracking(true)
- shadeRepository.setLegacyShadeExpansion(.5f)
+ shadeTestUtil.setTracking(true)
+ shadeTestUtil.setShadeExpansion(.5f)
runCurrent()
// THEN action was run
@@ -98,9 +91,9 @@
@Test
fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN isInteracting starts true
- shadeRepository.setLegacyShadeTracking(true)
+ shadeTestUtil.setTracking(true)
runCurrent()
detector.enable(action)
@@ -110,33 +103,34 @@
@Test
fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN shade is closed and detector is enabled
- shadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
detector.enable(action)
runCurrent()
// WHEN shade expands fully instantly
- shadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN action not run
verifyZeroInteractions(action)
+ detector.disable()
}
@Test
fun disableDetector_shouldNotPostRunnable() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN shade is closed and detector is enabled
- shadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
detector.enable(action)
runCurrent()
// WHEN detector is disabled and shade opens
detector.disable()
runCurrent()
- shadeRepository.setLegacyShadeTracking(true)
- shadeRepository.setLegacyShadeExpansion(.5f)
+ shadeTestUtil.setTracking(true)
+ shadeTestUtil.setShadeExpansion(.5f)
runCurrent()
// THEN action not run
@@ -145,17 +139,18 @@
@Test
fun enableDetector_beginCollapse_shouldNotPostRunnable() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN shade is open and detector is enabled
- shadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
detector.enable(action)
runCurrent()
// WHEN shade begins to collapse
- shadeRepository.setLegacyShadeExpansion(.5f)
+ shadeTestUtil.programmaticCollapseShade()
runCurrent()
// THEN action not run
verifyZeroInteractions(action)
+ detector.disable()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
index d44e26c..e32086b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -34,20 +34,21 @@
assertThat(viewModel.actions.value).isEmpty()
- viewModel.addAction(appearance, onclick)
+ viewModel.addAction(appearance, true, onclick)
assertThat(viewModel.actions.value).hasSize(1)
val added = viewModel.actions.value[0]
assertThat(added.appearance).isEqualTo(appearance)
assertThat(added.onClicked).isEqualTo(onclick)
+ assertThat(added.showDuringEntrance).isTrue()
}
@Test
fun testRemoveAction() {
val viewModel = ScreenshotViewModel(accessibilityManager)
- val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
- val secondId = viewModel.addAction(appearance, onclick)
+ val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), false, {})
+ val secondId = viewModel.addAction(appearance, false, onclick)
assertThat(viewModel.actions.value).hasSize(2)
assertThat(firstId).isNotEqualTo(secondId)
@@ -58,13 +59,14 @@
val remaining = viewModel.actions.value[0]
assertThat(remaining.appearance).isEqualTo(appearance)
+ assertThat(remaining.showDuringEntrance).isFalse()
assertThat(remaining.onClicked).isEqualTo(onclick)
}
@Test
fun testUpdateActionAppearance() {
val viewModel = ScreenshotViewModel(accessibilityManager)
- val id = viewModel.addAction(appearance, onclick)
+ val id = viewModel.addAction(appearance, false, onclick)
val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
viewModel.updateActionAppearance(id, otherAppearance)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 158f38d..347620a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -19,12 +19,13 @@
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -45,13 +46,16 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
-@RunWith(AndroidTestingRunner::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,11 +63,29 @@
private val testScope = kosmos.testScope
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
- private val shadeRepository = kosmos.shadeRepository
private val powerRepository = kosmos.powerRepository
private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
- val underTest = kosmos.footerViewModel
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: FooterViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.footerViewModel
+ }
@Test
fun messageVisible_whenFilteredNotifications() =
@@ -146,11 +168,9 @@
val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
- // WHEN shade is expanded
+ // WHEN shade is expanded AND QS not expanded
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- shadeRepository.setLegacyShadeExpansion(1f)
- // AND QS not expanded
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setShadeAndQsExpansion(1f, 0f)
// AND device is awake
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
@@ -182,9 +202,9 @@
// WHEN shade is collapsed
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- shadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
// AND QS not expanded
- shadeRepository.setQsExpansion(0f)
+ shadeTestUtil.setQsExpansion(0f)
// AND device is awake
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 82725d6..a6fb718 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -394,8 +394,20 @@
}
@Test
+ fun resetViewStates_shadeCollapsed_emptyShadeViewBecomesTransparent() {
+ ambientState.expansionFraction = 0f
+ stackScrollAlgorithm.initView(context)
+ hostView.removeAllViews()
+ hostView.addView(emptyShadeView)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+ assertThat(emptyShadeView.viewState.alpha).isEqualTo(0f)
+ }
+
+ @Test
fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() {
- ambientState.setStatusBarState(StatusBarState.SHADE)
+ ambientState.setStatusBarState(StatusBarState.KEYGUARD)
ambientState.fractionToShade = 0.25f
stackScrollAlgorithm.initView(context)
hostView.removeAllViews()
@@ -403,7 +415,8 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f)
+ val expected = getContentAlpha(ambientState.fractionToShade)
+ assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
index 78d4f02..140e919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -7,7 +7,6 @@
import com.android.systemui.user.data.repository.FakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -24,7 +23,7 @@
@Before
fun setUp() {
userRepository.setUserInfos(USER_INFOS)
- underTest = SelectedUserInteractor(TestCoroutineScope(), userRepository)
+ underTest = SelectedUserInteractor(userRepository)
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
new file mode 100644
index 0000000..ac135af
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeOneHandedModeRepository : OneHandedModeRepository {
+ private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+ override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+ return getFlow(userHandle.identifier)
+ }
+
+ override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean {
+ getFlow(userHandle.identifier).value = isEnabled
+ return true
+ }
+
+ /** initializes the flow if already not */
+ private fun getFlow(userId: Int): MutableStateFlow<Boolean> {
+ return userMap.getOrPut(userId) { MutableStateFlow(false) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
new file mode 100644
index 0000000..9ee200a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeOneHandedModeRepository by Kosmos.Fixture { FakeOneHandedModeRepository() }
+val Kosmos.oneHandedModeRepository by Kosmos.Fixture { fakeOneHandedModeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 2e2cf9a..0975687 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -81,7 +81,8 @@
com.android.systemui.Flags.constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
Utils.isDeviceCredentialAllowed(promptInfo) &&
- promptInfo.contentView != null
+ promptInfo.contentView != null &&
+ !promptInfo.isContentViewMoreOptionsButtonUsed
_showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index c7b06b6..7eef704 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -58,7 +58,7 @@
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
powerRepository: FakePowerRepository = FakePowerRepository(),
- userRepository: FakeUserRepository = FakeUserRepository()
+ userRepository: FakeUserRepository = FakeUserRepository(),
): WithDependencies {
val primaryBouncerInteractor =
PrimaryBouncerInteractor(
@@ -95,11 +95,7 @@
PowerInteractorFactory.create(
repository = powerRepository,
)
- val selectedUserInteractor =
- SelectedUserInteractor(
- applicationScope = testScope.backgroundScope,
- repository = userRepository
- )
+ val selectedUserInteractor = SelectedUserInteractor(repository = userRepository)
return WithDependencies(
trustRepository = trustRepository,
keyguardRepository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
new file mode 100644
index 0000000..d9c0361
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.onehanded
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsOneHandedModeTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideOneHandedTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 59a5bb5..38ede44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -59,11 +59,23 @@
delegate.setLockscreenShadeExpansion(lockscreenShadeExpansion)
}
- /** Sets whether the user is moving the shade with touch input. */
+ /** Sets whether the user is moving the shade with touch input on Lockscreen. */
fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
delegate.assertFlagValid()
delegate.setLockscreenShadeTracking(lockscreenShadeTracking)
}
+
+ /** Sets whether the user is moving the shade with touch input. */
+ fun setTracking(tracking: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setTracking(tracking)
+ }
+
+ /** Sets the shade to half collapsed with no touch input. */
+ fun programmaticCollapseShade() {
+ delegate.assertFlagValid()
+ delegate.programmaticCollapseShade()
+ }
}
/** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -80,11 +92,17 @@
/** Sets whether the user is moving the shade with touch input. */
fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean)
+ /** Sets whether the user is moving the shade with touch input. */
+ fun setTracking(tracking: Boolean)
+
/** Sets shade expansion to a value between 0-1. */
fun setShadeExpansion(shadeExpansion: Float)
/** Sets QS expansion to a value between 0-1. */
fun setQsExpansion(qsExpansion: Float)
+
+ /** Sets the shade to half collapsed with no touch input. */
+ fun programmaticCollapseShade()
}
/** Sets up shade state for tests when the scene container flag is disabled. */
@@ -104,6 +122,10 @@
shadeRepository.setLegacyLockscreenShadeTracking(lockscreenShadeTracking)
}
+ override fun setTracking(tracking: Boolean) {
+ shadeRepository.setLegacyShadeTracking(tracking)
+ }
+
override fun assertFlagValid() {
Assert.assertFalse(SceneContainerFlag.isEnabled)
}
@@ -119,6 +141,11 @@
shadeRepository.setQsExpansion(qsExpansion)
testScope.runCurrent()
}
+
+ override fun programmaticCollapseShade() {
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ testScope.runCurrent()
+ }
}
/** Sets up shade state for tests when the scene container flag is enabled. */
@@ -127,14 +154,16 @@
val isUserInputOngoing = MutableStateFlow(true)
override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
- if (shadeExpansion == 0f) {
- setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
- } else if (qsExpansion == 0f) {
- setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
- } else if (shadeExpansion == 1f) {
+ if (shadeExpansion == 1f) {
setIdleScene(Scenes.Shade)
} else if (qsExpansion == 1f) {
setIdleScene(Scenes.QuickSettings)
+ } else if (shadeExpansion == 0f && qsExpansion == 0f) {
+ setIdleScene(Scenes.Lockscreen)
+ } else if (shadeExpansion == 0f) {
+ setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion)
+ } else if (qsExpansion == 0f) {
+ setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion)
} else {
setTransitionProgress(Scenes.Shade, Scenes.QuickSettings, qsExpansion)
}
@@ -150,6 +179,10 @@
setShadeAndQsExpansion(0f, qsExpansion)
}
+ override fun programmaticCollapseShade() {
+ setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
+ }
+
override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
if (lockscreenShadeExpansion == 0f) {
setIdleScene(Scenes.Lockscreen)
@@ -161,7 +194,11 @@
}
override fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) {
- isUserInputOngoing.value = lockscreenShadeTracking
+ setTracking(lockscreenShadeTracking)
+ }
+
+ override fun setTracking(tracking: Boolean) {
+ isUserInputOngoing.value = tracking
}
private fun setIdleScene(scene: SceneKey) {
@@ -172,7 +209,12 @@
testScope.runCurrent()
}
- private fun setTransitionProgress(from: SceneKey, to: SceneKey, progress: Float) {
+ private fun setTransitionProgress(
+ from: SceneKey,
+ to: SceneKey,
+ progress: Float,
+ isInitiatedByUserInput: Boolean = true
+ ) {
sceneInteractor.changeScene(from, "test")
val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -181,7 +223,7 @@
toScene = to,
currentScene = flowOf(to),
progress = MutableStateFlow(progress),
- isInitiatedByUserInput = true,
+ isInitiatedByUserInput = isInitiatedByUserInput,
isUserInputOngoing = isUserInputOngoing,
)
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1f2ecb7..3e9ae4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -37,7 +37,7 @@
class FakeUserRepository @Inject constructor() : UserRepository {
companion object {
// User id to represent a non system (human) user id. We presume this is the main user.
- const val MAIN_USER_ID = 10
+ private const val MAIN_USER_ID = 10
private const val DEFAULT_SELECTED_USER = 0
private val DEFAULT_SELECTED_USER_INFO =
@@ -84,10 +84,6 @@
override var isRefreshUsersPaused: Boolean = false
- override suspend fun getMainUserId(): Int? {
- return MAIN_USER_ID
- }
-
var refreshUsersCallCount: Int = 0
private set
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
index 9dddfcd..89672f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
@@ -17,8 +17,6 @@
package com.android.systemui.user.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.data.repository.userRepository
-val Kosmos.selectedUserInteractor by
- Kosmos.Fixture { SelectedUserInteractor(applicationCoroutineScope, userRepository) }
+val Kosmos.selectedUserInteractor by Kosmos.Fixture { SelectedUserInteractor(userRepository) }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e06f400..bc608c5 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -16,6 +16,38 @@
visibility: ["//visibility:public"],
}
+filegroup {
+ name: "ravenwood-services-policies",
+ srcs: [
+ "texts/ravenwood-services-policies.txt",
+ ],
+ visibility: ["//visibility:public"],
+}
+
+filegroup {
+ name: "ravenwood-framework-policies",
+ srcs: [
+ "texts/ravenwood-framework-policies.txt",
+ ],
+ visibility: ["//visibility:public"],
+}
+
+filegroup {
+ name: "ravenwood-standard-options",
+ srcs: [
+ "texts/ravenwood-standard-options.txt",
+ ],
+ visibility: ["//visibility:public"],
+}
+
+filegroup {
+ name: "ravenwood-annotation-allowed-classes",
+ srcs: [
+ "texts/ravenwood-annotation-allowed-classes.txt",
+ ],
+ visibility: ["//visibility:public"],
+}
+
java_library {
name: "ravenwood-annotations-lib",
srcs: [":ravenwood-annotations"],
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index beacde2..cf58bd2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -24,6 +24,8 @@
# Where the input files are.
path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
+timestamp="$(date --iso-8601=seconds)"
+
m() {
${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
}
@@ -39,15 +41,15 @@
local jar=$1
local file=$2
- # Use sed to remove the header + prepend the jar filename.
- sed -e '1d' -e "s/^/$jar,/" $file
+ # Remove the header row, and prepend the columns.
+ sed -e '1d' -e "s/^/$jar,$timestamp,/" $file
}
collect_stats() {
local out="$1"
{
# Copy the header, with the first column appended.
- echo -n "Jar,"
+ echo -n "Jar,Generated Date,"
head -n 1 hoststubgen_framework-minus-apex_stats.csv
dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
@@ -61,7 +63,7 @@
local out="$1"
{
# Copy the header, with the first column appended.
- echo -n "Jar,"
+ echo -n "Jar,Generated Date,"
head -n 1 hoststubgen_framework-minus-apex_apis.csv
dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv
diff --git a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
similarity index 100%
rename from ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
rename to ravenwood/texts/ravenwood-framework-policies.txt
diff --git a/ravenwood/texts/services.core-ravenwood-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
similarity index 100%
rename from ravenwood/texts/services.core-ravenwood-policies.txt
rename to ravenwood/texts/ravenwood-services-policies.txt
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5af9424..3cea014 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4552,10 +4552,9 @@
pw.println(" 1: crop_windows");
pw.println(" 2: resizeable");
pw.println(" 3: resizeable_and_pipable");
- pw.println(" resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
- pw.println(" Makes sure <TASK_ID> is in a stack with the specified bounds.");
- pw.println(" Forces the task to be resizeable and creates a stack if no existing stack");
- pw.println(" has the specified bounds.");
+ pw.println(" resize <TASK_ID> <LEFT> <TOP> <RIGHT> <BOTTOM>");
+ pw.println(" The task is resized only if it is in multi-window windowing");
+ pw.println(" mode or freeform windowing mode.");
pw.println(" update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
pw.println(" Update the ApplicationInfo objects of the listed packages for <USER_ID>");
pw.println(" without restarting any processes.");
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index a46975fb..11ef577 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -285,7 +285,8 @@
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
- if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) {
+ if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null
+ && displayDeviceConfig.isEvenDimmerAvailable()) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
displayDeviceConfig));
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index a3dfe22..7ba4a4d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -87,9 +87,7 @@
mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
/* def= */ MIN_NITS_DEFAULT, userId);
- boolean isActive = Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 0, userId) == 1.0f && mAutoBrightnessEnabled;
+ boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled;
float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
@@ -202,6 +200,17 @@
pw.println(" mMinNitsAllowed=" + mMinNitsAllowed);
}
+ /**
+ * Defaults to true, on devices where setting is unset.
+ *
+ * @return if setting indicates feature is enabled
+ */
+ private boolean isSettingEnabled() {
+ return Settings.Secure.getFloatForUser(mContentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED,
+ /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f;
+ }
+
private float getBrightnessFromNits(float nits) {
return mDisplayDeviceConfig.getBrightnessFromBacklight(
mDisplayDeviceConfig.getBacklightFromNits(nits));
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 61514ab..d2d0279 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -304,6 +304,10 @@
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
private HdmiCecConfig mHdmiCecConfig;
+ // Timeout value for start ARC action after an established eARC connection was terminated,
+ // e.g. because eARC was disabled in Settings.
+ private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
+
/**
* Interface to report send result.
*/
@@ -5041,7 +5045,12 @@
// AudioService here that the eARC connection is terminated.
HdmiLogger.debug("eARC state change [new: HDMI_EARC_STATUS_ARC_PENDING(2)]");
notifyEarcStatusToAudioService(false, new ArrayList<>());
- startArcAction(true, null);
+ mHandler.postDelayed( new Runnable() {
+ @Override
+ public void run() {
+ startArcAction(true, null);
+ }
+ }, EARC_TRIGGER_START_ARC_ACTION_DELAY);
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
} else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 14d04119..1b9d6c5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -307,14 +307,17 @@
@MultiUserUnawareField
private final InputMethodMenuController mMenuController;
@MultiUserUnawareField
- @NonNull private final AutofillSuggestionsController mAutofillController;
+ @NonNull
+ private final AutofillSuggestionsController mAutofillController;
@GuardedBy("ImfLock.class")
@MultiUserUnawareField
- @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
+ @NonNull
+ private final ImeVisibilityStateComputer mVisibilityStateComputer;
@GuardedBy("ImfLock.class")
- @NonNull private final DefaultImeVisibilityApplier mVisibilityApplier;
+ @NonNull
+ private final DefaultImeVisibilityApplier mVisibilityApplier;
/**
* Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -363,7 +366,8 @@
@MultiUserUnawareField
private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
- @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Nullable
+ private StatusBarManagerInternal mStatusBarManagerInternal;
private boolean mShowOngoingImeSwitcherForPhones;
@GuardedBy("ImfLock.class")
@MultiUserUnawareField
@@ -538,7 +542,8 @@
* The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
*/
@MultiUserUnawareField
- @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+ @Nullable
+ IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
/**
* The {@link EditorInfo} last provided by the current client.
@@ -804,7 +809,8 @@
mRegistered = true;
}
- @Override public void onChange(boolean selfChange, Uri uri) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
final Uri showImeUri = Settings.Secure.getUriFor(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
@@ -897,10 +903,10 @@
}
for (int userId : mUserManagerInternal.getUserIds()) {
final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, settings);
}
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
@@ -1403,7 +1409,7 @@
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
- InputMethodManagerService.this);
+ InputMethodManagerService.this);
mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
bindingControllerForTesting != null ? bindingControllerForTesting
: bindingControllerFactory);
@@ -2416,7 +2422,8 @@
@FunctionalInterface
interface ImeDisplayValidator {
- @DisplayImePolicy int getDisplayImePolicy(int displayId);
+ @DisplayImePolicy
+ int getDisplayImePolicy(int displayId);
}
/**
@@ -2711,7 +2718,7 @@
: null;
if (mStatusBarManagerInternal != null) {
mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
- contentDescription != null
+ contentDescription != null
? contentDescription.toString() : null);
mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
}
@@ -4705,7 +4712,7 @@
SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
- AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
+ AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
if (sessionState != null) {
disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
sessionState.mSession);
@@ -5440,7 +5447,7 @@
}
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
- settings.getMethodMap().get(imeId))) {
+ settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
@@ -6583,6 +6590,7 @@
private final InputMethodManagerService mImms;
@NonNull
private final IBinder mToken;
+
InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
@NonNull IBinder token) {
mImms = imms;
@@ -6611,8 +6619,7 @@
@Override
public void createInputContentUriToken(Uri contentUri, String packageName,
AndroidFuture future /* T=IBinder */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<IBinder> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
try {
typedFuture.complete(mImms.createInputContentUriToken(
mToken, contentUri, packageName).asBinder());
@@ -6630,8 +6637,7 @@
@BinderThread
@Override
public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
mImms.setInputMethod(mToken, id);
typedFuture.complete(null);
@@ -6644,8 +6650,7 @@
@Override
public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype,
AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
mImms.setInputMethodAndSubtype(mToken, id, subtype);
typedFuture.complete(null);
@@ -6659,8 +6664,7 @@
public void hideMySoftInput(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
mImms.hideMySoftInput(mToken, statsToken, flags, reason);
typedFuture.complete(null);
@@ -6674,8 +6678,7 @@
public void showMySoftInput(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
AndroidFuture future /* T=Void */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Void> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
mImms.showMySoftInput(mToken, statsToken, flags, reason);
typedFuture.complete(null);
@@ -6693,8 +6696,7 @@
@BinderThread
@Override
public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
typedFuture.complete(mImms.switchToPreviousInputMethod(mToken));
} catch (Throwable e) {
@@ -6706,8 +6708,7 @@
@Override
public void switchToNextInputMethod(boolean onlyCurrentIme,
AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
} catch (Throwable e) {
@@ -6718,8 +6719,7 @@
@BinderThread
@Override
public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
- @SuppressWarnings("unchecked")
- final AndroidFuture<Boolean> typedFuture = future;
+ @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken));
} catch (Throwable e) {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8a85328..71a7d0d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -46,6 +46,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.LocalLog;
+import android.util.MutableBoolean;
import android.util.Pair;
import android.util.Slog;
import android.util.Xml;
@@ -104,6 +105,7 @@
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportAllowlistedPackages;
private final BugreportFileManager mBugreportFileManager;
+ private static final FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
@GuardedBy("mLock")
@@ -429,9 +431,51 @@
ensureUserCanTakeBugReport(bugreportMode);
Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
- synchronized (mLock) {
- startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
- bugreportMode, bugreportFlags, listener, isScreenshotRequested);
+ final MutableBoolean handoffLock = new MutableBoolean(false);
+ if (sFeatureFlags.asyncStartBugreport()) {
+ synchronized (handoffLock) {
+ new Thread(()-> {
+ try {
+ synchronized (mLock) {
+ synchronized (handoffLock) {
+ handoffLock.value = true;
+ handoffLock.notifyAll();
+ }
+ startBugreportLocked(
+ callingUid,
+ callingPackage,
+ bugreportFd,
+ screenshotFd,
+ bugreportMode,
+ bugreportFlags,
+ listener,
+ isScreenshotRequested);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Cannot start a new bugreport due to an unknown error", e);
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+ }
+ }, "BugreportManagerServiceThread").start();
+ try {
+ while (!handoffLock.value) { // handle the rare case of a spurious wakeup
+ handoffLock.wait(DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS);
+ }
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Unexpectedly interrupted waiting for startBugreportLocked", e);
+ }
+ }
+ } else {
+ synchronized (mLock) {
+ startBugreportLocked(
+ callingUid,
+ callingPackage,
+ bugreportFd,
+ screenshotFd,
+ bugreportMode,
+ bugreportFlags,
+ listener,
+ isScreenshotRequested);
+ }
}
}
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index ae33df8..efdc9b8 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -7,3 +7,13 @@
description: "Use proto tombstones as source of truth for adding to dropbox"
bug: "323857385"
}
+
+flag {
+ name: "async_start_bugreport"
+ namespace: "crumpet"
+ description: "Don't block callers on the start of dumpsys service"
+ bug: "180123623"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 3862b79..5ac883c 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -688,6 +688,29 @@
);
}
+ /** Call intent should be handled by the main user. */
+ private static final DefaultCrossProfileIntentFilter CALL_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ SKIP_CURRENT_PROFILE,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_CALL)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addDataScheme("tel")
+ .addDataScheme("sip")
+ .addDataScheme("voicemail")
+ .build();
+
+ /** Pressing the call button should be handled by the main user. */
+ private static final DefaultCrossProfileIntentFilter CALL_BUTTON_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ ONLY_IF_NO_MATCH_FOUND,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_CALL_BUTTON)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .build();
+
/** Dial intent with mime type can be handled by either private profile or its parent user. */
private static final DefaultCrossProfileIntentFilter DIAL_MIME_PRIVATE_PROFILE =
new DefaultCrossProfileIntentFilter.Builder(
@@ -755,6 +778,10 @@
DIAL_MIME_PRIVATE_PROFILE,
DIAL_DATA_PRIVATE_PROFILE,
DIAL_RAW_PRIVATE_PROFILE,
+ CALL_PRIVATE_PROFILE,
+ CALL_BUTTON_PRIVATE_PROFILE,
+ EMERGENCY_CALL_DATA,
+ EMERGENCY_CALL_MIME,
SMS_MMS_PRIVATE_PROFILE
);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index fa54f6e..b369f03 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1667,15 +1667,6 @@
if (appMetadataFile.exists()) {
return true;
}
- if (isSystem) {
- try {
- makeDirRecursive(new File(appMetadataFilePath).getParentFile(), 0700);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to create app metadata dir for package "
- + pkg.getPackageName() + ": " + e.getMessage());
- return false;
- }
- }
Map<String, Property> properties = pkg.getProperties();
if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
return false;
@@ -1684,6 +1675,15 @@
if (!fileInAPkPathProperty.isString()) {
return false;
}
+ if (isSystem && !appMetadataFile.getParentFile().exists()) {
+ try {
+ makeDirRecursive(appMetadataFile.getParentFile(), 0700);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to create app metadata dir for package "
+ + pkg.getPackageName() + ": " + e.getMessage());
+ return false;
+ }
+ }
String fileInApkPath = fileInAPkPathProperty.getString();
List<AndroidPackageSplit> splits = pkg.getSplits();
for (int i = 0; i < splits.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 82902d4..9edf3b1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
- static final boolean DEBUG_REBOOT = false; // STOPSHIP if true
+ static final boolean DEBUG_REBOOT = true;
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -3798,24 +3798,36 @@
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
+ Slog.d(TAG, "received package broadcast intent: " + intent);
switch (action) {
case Intent.ACTION_PACKAGE_ADDED:
if (replacing) {
+ Slog.d(TAG, "replacing package: " + packageName + " userId" + userId);
handlePackageUpdateFinished(packageName, userId);
} else {
+ Slog.d(TAG, "adding package: " + packageName + " userId" + userId);
handlePackageAdded(packageName, userId);
}
break;
case Intent.ACTION_PACKAGE_REMOVED:
if (!replacing || (replacing && archival)) {
+ if (!replacing) {
+ Slog.d(TAG, "removing package: "
+ + packageName + " userId" + userId);
+ } else if (archival) {
+ Slog.d(TAG, "archiving package: "
+ + packageName + " userId" + userId);
+ }
handlePackageRemoved(packageName, userId);
}
break;
case Intent.ACTION_PACKAGE_CHANGED:
+ Slog.d(TAG, "changing package: " + packageName + " userId" + userId);
handlePackageChanged(packageName, userId);
-
break;
case Intent.ACTION_PACKAGE_DATA_CLEARED:
+ Slog.d(TAG, "clearing data for package: "
+ + packageName + " userId" + userId);
handlePackageDataCleared(packageName, userId);
break;
}
diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
index 059c4a4..1dea523 100644
--- a/services/core/java/com/android/server/power/batterysaver/flags.aconfig
+++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig
@@ -3,7 +3,7 @@
flag {
name: "update_auto_turn_on_notification_string_and_action"
- namespace: "power_optimization"
+ namespace: "backstage_power"
description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification"
bug: "336960905"
metadata {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 3619253..47425322 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -115,6 +115,10 @@
// validation failure.
private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
+ /** Carriers can disable the detector by setting the threshold to -1 */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR = -1;
+
private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
// By default, there's no maximum limit enforced
@@ -271,7 +275,10 @@
// When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
// enabled on the last one as a sample
mInboundTransform = inboundTransform;
- start();
+
+ if (!Flags.allowDisableIpsecLossDetector() || canStart()) {
+ start();
+ }
}
@Override
@@ -284,6 +291,14 @@
mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
}
+
+ if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) {
+ if (canStart()) {
+ start();
+ } else {
+ stop();
+ }
+ }
}
@Override
@@ -298,6 +313,12 @@
mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
}
+ private boolean canStart() {
+ return mInboundTransform != null
+ && mPacketLossRatePercentThreshold
+ != IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
+ }
+
@Override
protected void start() {
super.start();
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 1c59977..6aa0039 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -80,8 +80,8 @@
LaunchParamsController.LaunchParams outParams) {
if (!canEnterDesktopMode(mContext)) {
- appendLog("desktop mode is not enabled, continuing");
- return RESULT_CONTINUE;
+ appendLog("desktop mode is not enabled, skipping");
+ return RESULT_SKIP;
}
if (task == null) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 8e78d25..6dec712 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -708,26 +708,6 @@
}
}
- /**
- * Removes the oldest recent task that is compatible with the given one. This is possible if
- * the task windowing mode changed after being added to the Recents.
- */
- void removeCompatibleRecentTask(Task task) {
- final int taskIndex = mTasks.indexOf(task);
- if (taskIndex < 0) {
- return;
- }
-
- final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
- if (candidateIndex == -1) {
- // Nothing to trim
- return;
- }
-
- final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
- remove(taskToRemove);
- }
-
void removeTasksByPackageName(String packageName, int userId) {
for (int i = mTasks.size() - 1; i >= 0; --i) {
final Task task = mTasks.get(i);
@@ -1615,10 +1595,6 @@
* list (if any).
*/
private int findRemoveIndexForAddTask(Task task) {
- return findRemoveIndexForTask(task, true /* includingSelf */);
- }
-
- private int findRemoveIndexForTask(Task task, boolean includingSelf) {
final int recentsCount = mTasks.size();
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
@@ -1674,8 +1650,6 @@
// existing task
continue;
}
- } else if (!includingSelf) {
- continue;
}
return i;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5f13672..a9c47b8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6888,7 +6888,7 @@
mIsBoosted = isBoosted;
// The client transaction will be applied together with the next assignLayer.
if (clientTransaction != null) {
- mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ mPendingClientTransactions.add(clientTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1573d09..90e7bd7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1916,7 +1916,6 @@
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
final Task task = tasksToReparent.get(i);
- final int prevWindowingMode = task.getWindowingMode();
if (syncId >= 0) {
addToSyncSet(syncId, task);
}
@@ -1930,12 +1929,6 @@
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
}
- // Trim the compatible Recent task (if any) after the Task is reparented and now has
- // a different windowing mode, in order to prevent redundant Recent tasks after
- // reparenting.
- if (prevWindowingMode != task.getWindowingMode()) {
- mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
- }
}
if (transition != null) transition.collect(newParent);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dd173af..375fc5a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -349,8 +349,8 @@
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.PackageSetPolicyValue;
import android.app.admin.StartInstallingUpdateCallback;
-import android.app.admin.StringSetPolicyValue;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.UnsafeStateException;
@@ -12073,7 +12073,7 @@
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
admin,
- new StringSetPolicyValue(new HashSet<>(packageList)),
+ new PackageSetPolicyValue(new HashSet<>(packageList)),
userId);
}
}
@@ -20358,12 +20358,12 @@
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
enforcingAdmin,
- new StringSetPolicyValue(packages));
+ new PackageSetPolicyValue(packages));
} else {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
enforcingAdmin,
- new StringSetPolicyValue(packages),
+ new PackageSetPolicyValue(packages),
caller.getUserId());
}
}
@@ -24045,7 +24045,7 @@
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
enforcingAdmin,
- new StringSetPolicyValue(
+ new PackageSetPolicyValue(
new HashSet<>(admin.permittedInputMethods)),
admin.getUserHandle().getIdentifier());
}
@@ -24054,7 +24054,7 @@
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
enforcingAdmin,
- new StringSetPolicyValue(
+ new PackageSetPolicyValue(
new HashSet<>(admin.getParentActiveAdmin()
.permittedInputMethods)),
getProfileParentId(admin.getUserHandle().getIdentifier()));
@@ -24110,12 +24110,14 @@
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
enforcingAdmin,
- new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)));
+ new PackageSetPolicyValue(
+ new HashSet<>(admin.protectedPackages)));
} else {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
enforcingAdmin,
- new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)),
+ new PackageSetPolicyValue(
+ new HashSet<>(admin.protectedPackages)),
admin.getUserHandle().getIdentifier());
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
similarity index 87%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
index a9d65ac..c4da029 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
import android.util.Log;
import com.android.modules.utils.TypedXmlPullParser;
@@ -30,7 +30,7 @@
import java.util.Set;
// TODO(scottjonathan): Replace with generic set implementation
-final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
+final class PackageSetPolicySerializer extends PolicySerializer<Set<String>> {
private static final String ATTR_VALUES = "strings";
private static final String ATTR_VALUES_SEPARATOR = ";";
@Override
@@ -45,10 +45,10 @@
PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
if (valuesStr == null) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
+ Log.e(DevicePolicyEngine.TAG, "Error parsing PackageSet policy value.");
return null;
}
Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR));
- return new StringSetPolicyValue(values);
+ return new PackageSetPolicyValue(values);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
similarity index 79%
rename from services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
index 5298960..d1e241b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java
@@ -18,14 +18,15 @@
import android.annotation.NonNull;
import android.app.admin.PolicyValue;
-import android.app.admin.StringSetPolicyValue;
+import android.app.admin.PackageSetPolicyValue;
+import android.app.admin.StringSetUnion;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
-final class StringSetUnion extends ResolutionMechanism<Set<String>> {
+final class PackageSetUnion extends ResolutionMechanism<Set<String>> {
@Override
PolicyValue<Set<String>> resolve(
@@ -38,17 +39,17 @@
for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
unionOfPolicies.addAll(policy.getValue());
}
- return new StringSetPolicyValue(unionOfPolicies);
+ return new PackageSetPolicyValue(unionOfPolicies);
}
@Override
- android.app.admin.StringSetUnion getParcelableResolutionMechanism() {
- return new android.app.admin.StringSetUnion();
+ StringSetUnion getParcelableResolutionMechanism() {
+ return new StringSetUnion();
}
@Override
public String toString() {
- return "SetUnion {}";
+ return "PackageSetUnion {}";
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 9a73d5e..8d980b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -162,9 +162,9 @@
new PolicyDefinition<>(
new NoArgsPolicyKey(
DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
- new StringSetUnion(),
+ new PackageSetUnion(),
PolicyEnforcerCallbacks::setUserControlDisabledPackages,
- new StringSetPolicySerializer());
+ new PackageSetPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
// actual policy with the correct arguments (i.e. packageName) when reading the policies from
@@ -328,7 +328,7 @@
new MostRecent<>(),
POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE,
(Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true,
- new StringSetPolicySerializer());
+ new PackageSetPolicySerializer());
static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 9a92c70..e1b66b5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -106,6 +106,7 @@
private HdmiPortInfo[] mHdmiPortInfo;
private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
private static final int PORT_ID_EARC_SUPPORTED = 3;
+ private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500;
@Before
public void setUp() throws Exception {
@@ -1374,6 +1375,11 @@
PORT_ID_EARC_SUPPORTED);
verify(mHdmiControlServiceSpy, times(1))
.notifyEarcStatusToAudioService(eq(false), eq(new ArrayList<>()));
+ // ARC should be never initiated here. It should be started after 500 ms.
+ verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+ // We move 500 ms forward because the action is only started 500 ms later.
+ mTestLooper.moveTimeForward(EARC_TRIGGER_START_ARC_ACTION_DELAY);
+ mTestLooper.dispatchAll();
verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index f92387c..a268aa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -79,19 +79,19 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsContinueIfDesktopWindowingIsDisabled() {
+ public void testReturnsSkipIfDesktopWindowingIsDisabled() {
setupDesktopModeLaunchParamsModifier();
- assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- public void testReturnsContinueIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
+ public void testReturnsSkipIfDesktopWindowingIsEnabledOnUnsupportedDevice() {
setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ false,
/*enforceDeviceRestrictions=*/ true);
- assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 75b84d1..6ec1429 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1373,26 +1373,6 @@
assertTrue(info.supportsMultiWindow);
}
- @Test
- public void testRemoveCompatibleRecentTask() {
- final Task task1 = createTaskBuilder(".Task").setWindowingMode(
- WINDOWING_MODE_FULLSCREEN).build();
- mRecentTasks.add(task1);
- final Task task2 = createTaskBuilder(".Task").setWindowingMode(
- WINDOWING_MODE_MULTI_WINDOW).build();
- mRecentTasks.add(task2);
- assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
-
- // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
- task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- mRecentTasks.removeCompatibleRecentTask(task2);
- assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
- assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
- true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
- }
-
private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
HardwareBuffer buffer = null;
if (bufferSize != null) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4c719dd..bc8f65e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3888,7 +3888,7 @@
/**
* Whether device resets all of NR timers when device is in a voice call and QOS is established.
- * The default value is false;
+ * The default value is true;
*
* @see #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING
* @see #KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING
@@ -10909,7 +10909,7 @@
sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0);
sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false);
- sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false);
+ sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true);
sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false);
/* Default value is 1 hour. */
sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index f161f31..0ecafc7 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1283,6 +1283,8 @@
private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
+ private static final String SINGAPORE_ISO_COUNTRY_CODE = "SG";
+
/**
* Breaks the given number down and formats it according to the rules
* for the country the number is from.
@@ -1669,6 +1671,17 @@
* dialing format.
*/
result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
+ } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() &&
+ (SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
+ pn.getCountryCode() ==
+ util.getCountryCodeForRegion(SINGAPORE_ISO_COUNTRY_CODE) &&
+ (pn.getCountryCodeSource() ==
+ PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) {
+ /*
+ * Need to reformat Singaporean phone numbers (when the user is in Singapore)
+ * with the country code (+65) removed to comply with Singaporean regulations.
+ */
+ result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
} else {
result = util.formatInOriginalFormat(pn, defaultCountryIso);
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index c8b60e5..441a4ae 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -20,6 +20,7 @@
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
@@ -584,4 +585,56 @@
MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
getMaxSeqNumIncreasePerSecond(mCarrierConfig));
}
+
+ private IpSecPacketLossDetector newDetectorAndSetTransform(int threshold) throws Exception {
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+ anyInt()))
+ .thenReturn(threshold);
+
+ final IpSecPacketLossDetector detector =
+ new IpSecPacketLossDetector(
+ mVcnContext,
+ mNetwork,
+ mCarrierConfig,
+ mMetricMonitorCallback,
+ mDependencies);
+
+ detector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+ detector.setInboundTransformInternal(mIpSecTransform);
+
+ return detector;
+ }
+
+ @Test
+ public void testDisableAndEnableDetectorWithCarrierConfig() throws Exception {
+ final IpSecPacketLossDetector detector =
+ newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+
+ assertFalse(detector.isStarted());
+
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+ anyInt()))
+ .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+ detector.setCarrierConfig(mCarrierConfig);
+
+ assertTrue(detector.isStarted());
+ }
+
+ @Test
+ public void testEnableAndDisableDetectorWithCarrierConfig() throws Exception {
+ final IpSecPacketLossDetector detector =
+ newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+ assertTrue(detector.isStarted());
+
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+ anyInt()))
+ .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR);
+ detector.setCarrierConfig(mCarrierConfig);
+
+ assertFalse(detector.isStarted());
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index edad678..0439d5f5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_DISABLE_IPSEC_LOSS_DETECTOR);
when(mNetwork.getNetId()).thenReturn(-1);
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index f1e4ead..669cddb 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -443,7 +443,7 @@
manifest_action.Action(AutoGenerateIsSplitRequired);
manifest_action.Action(VerifyManifest);
manifest_action.Action(FixCoreAppAttribute);
- manifest_action.Action([&](xml::Element* el) -> bool {
+ manifest_action.Action([this, diag](xml::Element* el) -> bool {
EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls);
if (options_.version_name_default) {
@@ -506,7 +506,7 @@
manifest_action["eat-comment"];
// Uses-sdk actions.
- manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool {
+ manifest_action["uses-sdk"].Action([this](xml::Element* el) -> bool {
if (options_.min_sdk_version_default &&
el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) {
// There was no minSdkVersion defined and we have a default to assign.
@@ -528,7 +528,7 @@
// Instrumentation actions.
manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
- manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool {
+ manifest_action["instrumentation"].Action([this](xml::Element* el) -> bool {
if (!options_.rename_instrumentation_target_package) {
return true;
}
@@ -544,7 +544,7 @@
manifest_action["attribution"];
manifest_action["attribution"]["inherit-from"];
manifest_action["original-package"];
- manifest_action["overlay"].Action([&](xml::Element* el) -> bool {
+ manifest_action["overlay"].Action([this](xml::Element* el) -> bool {
if (options_.rename_overlay_target_package) {
if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) {
attr->value = options_.rename_overlay_target_package.value();
@@ -625,7 +625,7 @@
uses_package_action["additional-certificate"];
if (options_.debug_mode) {
- application_action.Action([&](xml::Element* el) -> bool {
+ application_action.Action([](xml::Element* el) -> bool {
xml::Attribute *attr = el->FindOrCreateAttribute(xml::kSchemaAndroid, "debuggable");
attr->value = "true";
return true;
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 5659a35..2e144f5 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -15,11 +15,11 @@
*/
package com.android.hoststubgen.filters
-import com.android.hoststubgen.UnknownApiException
import com.android.hoststubgen.addNonNullElement
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.asm.toHumanReadableMethodName
+import com.android.hoststubgen.log
// TODO: Validate all input names.
@@ -48,30 +48,30 @@
return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
}
- private fun ensureClassExists(className: String) {
+ private fun checkClass(className: String) {
if (classes.findClass(className) == null) {
- throw UnknownApiException("Unknown class $className")
+ log.w("Unknown class $className")
}
}
- private fun ensureFieldExists(className: String, fieldName: String) {
+ private fun checkField(className: String, fieldName: String) {
if (classes.findField(className, fieldName) == null) {
- throw UnknownApiException("Unknown field $className.$fieldName")
+ log.w("Unknown field $className.$fieldName")
}
}
- private fun ensureMethodExists(
+ private fun checkMethod(
className: String,
methodName: String,
descriptor: String
) {
if (classes.findMethod(className, methodName, descriptor) == null) {
- throw UnknownApiException("Unknown method $className.$methodName$descriptor")
+ log.w("Unknown method $className.$methodName$descriptor")
}
}
fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
- ensureClassExists(className)
+ checkClass(className)
mPolicies[getClassKey(className)] = policy
}
@@ -81,7 +81,7 @@
}
fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
- ensureFieldExists(className, fieldName)
+ checkField(className, fieldName)
mPolicies[getFieldKey(className, fieldName)] = policy
}
@@ -100,7 +100,7 @@
descriptor: String,
policy: FilterPolicyWithReason,
) {
- ensureMethodExists(className, methodName, descriptor)
+ checkMethod(className, methodName, descriptor)
mPolicies[getMethodKey(className, methodName, descriptor)] = policy
}
@@ -110,8 +110,8 @@
}
fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
- ensureMethodExists(className, methodName, descriptor)
- ensureMethodExists(className, toName, descriptor)
+ checkMethod(className, methodName, descriptor)
+ checkMethod(className, toName, descriptor)
mRenames[getMethodKey(className, methodName, descriptor)] = toName
}
@@ -121,7 +121,7 @@
}
fun setNativeSubstitutionClass(from: String, to: String) {
- ensureClassExists(from)
+ checkClass(from)
// Native substitute classes may be provided from other jars, so we can't do this check.
// ensureClassExists(to)