Merge "Hook up userId in IMMS#showInputMethodPicker()" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 857154f..9a178e5 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4495,8 +4495,9 @@
final int[] userIds =
mUserWakeupStore.getUserIdsToWakeup(nowELAPSED);
for (int i = 0; i < userIds.length; i++) {
- if (!mActivityManagerInternal.startUserInBackground(
- userIds[i])) {
+ if (mActivityManagerInternal.isUserRunning(userIds[i], 0)
+ || !mActivityManagerInternal.startUserInBackground(
+ userIds[i])) {
mUserWakeupStore.removeUserWakeup(userIds[i]);
}
}
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e95c6a4..8aec7eb 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -425,7 +425,7 @@
private static IBinder rawGetService(String name) throws RemoteException {
final long start = sStatLogger.getTime();
- final IBinder binder = getIServiceManager().getService(name).getBinder();
+ final IBinder binder = getIServiceManager().getService2(name).getBinder();
final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 6c9a5c7..5a9c878 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -57,8 +57,14 @@
return mRemote;
}
+ // TODO(b/355394904): This function has been deprecated, please use getService2 instead.
@UnsupportedAppUsage
- public Service getService(String name) throws RemoteException {
+ public IBinder getService(String name) throws RemoteException {
+ // Same as checkService (old versions of servicemanager had both methods).
+ return checkService(name).getBinder();
+ }
+
+ public Service getService2(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
return checkService(name);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 850b979..c186538 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12560,6 +12560,14 @@
"contextual_screen_timeout_enabled";
/**
+ * Whether hinge angle lidevent is enabled.
+ *
+ * @hide
+ */
+ public static final String HINGE_ANGLE_LIDEVENT_ENABLED =
+ "hinge_angle_lidevent_enabled";
+
+ /**
* Whether lockscreen weather is enabled.
*
* @hide
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 64c7766..5f8bea1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34071,14 +34071,21 @@
}
private float convertVelocityToFrameRate(float velocityPps) {
- // From UXR study, premium experience is:
- // 1500+ dp/s: 120fps
- // 0 - 1500 dp/s: 80fps
- // OEMs are likely to modify this to balance battery and user experience for their
- // specific device.
+ // Internal testing has shown that this gives a premium experience:
+ // above 300dp/s => 120fps
+ // between 300dp/s and 125fps => 80fps
+ // below 125dp/s => 60fps
float density = mAttachInfo.mDensity;
float velocityDps = velocityPps / density;
- return (velocityDps >= 1500f) ? MAX_FRAME_RATE : 80f;
+ float frameRate;
+ if (velocityDps > 300f) {
+ frameRate = MAX_FRAME_RATE; // Use maximum at fast motion
+ } else if (velocityDps > 125f) {
+ frameRate = 80f; // Use medium frame rate when motion is slower
+ } else {
+ frameRate = 60f; // Use minimum frame rate when motion is very slow
+ }
+ return frameRate;
}
/**
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index d505c733..95cae226 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -582,4 +582,9 @@
Log.e(TAG, "authenticate() failed for intent:" + intent, e);
}
}
+
+ @Override
+ public boolean isActivityResumed() {
+ return mActivity.isResumed();
+ }
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 02a86c9..79ecfe1e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -114,6 +114,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -774,6 +775,13 @@
// dataset in responses. Used to avoid request pre-fill request again and again.
private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();
+ // Whether we need to re-attempt fill again. Needed for case of relayout.
+ private boolean mFillReAttemptNeeded = false;
+
+ private Map<Integer, AutofillId> mFingerprintToViewMap = new ArrayMap<>();
+
+ private AutofillStateFingerprint mAutofillStateFingerprint;
+
/** @hide */
public interface AutofillClient {
/**
@@ -909,6 +917,11 @@
* @return An ID that is unique in the activity.
*/
@Nullable AutofillId autofillClientGetNextAutofillId();
+
+ /**
+ * @return Whether the activity is resumed or not.
+ */
+ boolean isActivityResumed();
}
/**
@@ -919,6 +932,7 @@
mService = service;
mOptions = context.getAutofillOptions();
mIsFillRequested = new AtomicBoolean(false);
+ mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();
mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
@@ -1357,6 +1371,20 @@
mOnInvisibleCalled = true;
if (isExpiredResponse) {
+ if (mRelayoutFix && isAuthenticationPending()) {
+ Log.i(TAG, "onInvisibleForAutofill(): Ignoring expiringResponse due to pending"
+ + " authentication");
+ try {
+ mService.notifyNotExpiringResponseDuringAuth(
+ mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ return;
+ }
+ Log.i(TAG, "onInvisibleForAutofill(): expiringResponse");
// Notify service the response has expired.
updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
@@ -1513,14 +1541,29 @@
}
/**
+ * Called to log notify view entered was ignored due to pending auth
+ * @hide
+ */
+ public void notifyViewEnteredIgnoredDuringAuthCount() {
+ try {
+ mService.notifyViewEnteredIgnoredDuringAuthCount(mSessionId, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ }
+
+ /**
* Called to check if we should retry fill.
* Useful for knowing whether to attempt refill after relayout.
*
* @hide
*/
public boolean shouldRetryFill() {
- // TODO: Implement in follow-up cl
- return false;
+ synchronized (mLock) {
+ return isAuthenticationPending() && mFillReAttemptNeeded;
+ }
}
/**
@@ -1531,8 +1574,13 @@
*/
public boolean attemptRefill() {
Log.i(TAG, "Attempting refill");
- // TODO: Implement in follow-up cl
- return false;
+ // Find active autofillable views. Compute their fingerprints
+ List<View> autofillableViews =
+ getClient().autofillClientFindAutofillableViewsByTraversal();
+ if (sDebug) {
+ Log.d(TAG, "Autofillable views count:" + autofillableViews.size());
+ }
+ return mAutofillStateFingerprint.attemptRefill(autofillableViews, this);
}
/**
@@ -2493,7 +2541,13 @@
/** @hide */
public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
+ if (sVerbose) {
+ Log.v(TAG, "onAuthenticationResult(): authId= " + authenticationId + ", data=" + data);
+ }
if (!hasAutofillFeature()) {
+ if (sVerbose) {
+ Log.v(TAG, "onAuthenticationResult(): autofill not enabled");
+ }
return;
}
// TODO: the result code is being ignored, so this method is not reliably
@@ -2501,10 +2555,6 @@
// set the EXTRA_AUTHENTICATION_RESULT extra, but it could cause weird results if the
// service set the extra and returned RESULT_CANCELED...
- if (sDebug) {
- Log.d(TAG, "onAuthenticationResult(): id= " + authenticationId + ", data=" + data);
- }
-
synchronized (mLock) {
if (!isActiveLocked()) {
Log.w(TAG, "onAuthenticationResult(): sessionId=" + mSessionId + " not active");
@@ -2661,6 +2711,7 @@
mSessionId = receiver.getIntResult();
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
+ mAutofillStateFingerprint.setSessionId(mSessionId);
}
final int extraFlags = receiver.getOptionalExtraIntResult(0);
if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
@@ -2722,6 +2773,9 @@
if (resetEnteredIds) {
mEnteredIds = null;
}
+ mFillReAttemptNeeded = false;
+ mFingerprintToViewMap.clear();
+ mAutofillStateFingerprint = AutofillStateFingerprint.createInstance();
}
@GuardedBy("mLock")
@@ -2984,8 +3038,12 @@
Intent fillInIntent, boolean authenticateInline) {
synchronized (mLock) {
if (sessionId == mSessionId) {
- if (mRelayoutFixDeprecated) {
+ if (mRelayoutFixDeprecated || mRelayoutFix) {
mState = STATE_PENDING_AUTHENTICATION;
+ if (sVerbose) {
+ Log.v(TAG, "entering STATE_PENDING_AUTHENTICATION : mRelayoutFix:"
+ + mRelayoutFix);
+ }
}
final AutofillClient client = getClient();
if (client != null) {
@@ -3191,17 +3249,56 @@
@GuardedBy("mLock")
private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) {
+ handleFailedIdsLocked(failedIds, null, false, false);
+ }
+
+ @GuardedBy("mLock")
+ private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds,
+ ArrayList<AutofillValue> failedAutofillValues, boolean hideHighlight,
+ boolean isRefill) {
if (!failedIds.isEmpty() && sVerbose) {
Log.v(TAG, "autofill(): total failed views: " + failedIds);
}
+
+ if (mRelayoutFix && !failedIds.isEmpty()) {
+ // Activity isn't in resumed state, so it's very possible that relayout could've
+ // occurred, so wait for it to declare proper failure. It's a temporary failure at the
+ // moment. We'll try again later when the activity is resumed.
+
+ // The above doesn't seem to be the correct way. Look for pending auth cases.
+ // TODO(b/238252288): Check whether there was any auth done at all
+ mFillReAttemptNeeded = true;
+ mAutofillStateFingerprint.storeFailedIdsAndValues(
+ failedIds, failedAutofillValues, hideHighlight);
+ }
try {
- mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+ mService.setAutofillFailure(mSessionId, failedIds, isRefill, mContext.getUserId());
} catch (RemoteException e) {
// In theory, we could ignore this error since it's not a big deal, but
// in reality, we rather crash the app anyways, as the failure could be
// a consequence of something going wrong on the server side...
throw e.rethrowFromSystemServer();
}
+ if (mRelayoutFix && !failedIds.isEmpty()) {
+ if (!getClient().isActivityResumed()) {
+ if (sVerbose) {
+ Log.v(TAG, "handleFailedIdsLocked(): failed id's exist, but activity not"
+ + " resumed");
+ }
+ } else {
+ if (isRefill) {
+ Log.i(TAG, "handleFailedIdsLocked(): Attempted refill, but failed");
+ } else {
+ // activity has been resumed, try to re-fill
+ // getClient().isActivityResumed() && !failedIds.isEmpty() && !isRefill
+ // TODO(b/238252288): Do better state management, and only trigger the following
+ // if there was auth previously.
+ Log.i(TAG, "handleFailedIdsLocked(): Attempting refill");
+ attemptRefill();
+ mFillReAttemptNeeded = false;
+ }
+ }
+ }
}
private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
@@ -3216,13 +3313,46 @@
return;
}
- final int itemCount = ids.size();
- int numApplied = 0;
- ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
Helper.toArray(ids));
+ autofill(views, ids, values, hideHighlight, false);
+ }
+ }
+
+ void autofill(View[] views, List<AutofillId> ids, List<AutofillValue> values,
+ boolean hideHighlight, boolean isRefill) {
+ if (sVerbose) {
+ Log.v(TAG, "autofill() ids:" + ids + " isRefill:" + isRefill);
+ }
+ synchronized (mLock) {
+ final AutofillClient client = getClient();
+ if (client == null) {
+ return;
+ }
+
+ if (ids == null) {
+ Log.i(TAG, "autofill(): No id's to fill");
+ return;
+ }
+
+ if (mRelayoutFix && isRefill) {
+ try {
+ mService.setAutofillIdsAttemptedForRefill(
+ mSessionId, ids, mContext.getUserId());
+ } catch (RemoteException e) {
+ // The failure could be a consequence of something going wrong on the
+ // server side. Do nothing here since it's just logging, but it's
+ // possible follow-up actions may fail.
+ }
+ }
+
+ final int itemCount = ids.size();
+ int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+
ArrayList<AutofillId> failedIds = new ArrayList<>();
+ ArrayList<AutofillValue> failedAutofillValues = new ArrayList<>();
if (mLastAutofilledData == null) {
mLastAutofilledData = new ParcelableMap(itemCount);
@@ -3237,7 +3367,9 @@
// the service; this is fine, but we need to update the view status in the
// server side so it can be triggered again.
Log.d(TAG, "autofill(): no View with id " + id);
+ // Possible relayout scenario
failedIds.add(id);
+ failedAutofillValues.add(value);
continue;
}
// Mark the view as to be autofilled with 'value'
@@ -3268,7 +3400,8 @@
}
}
- handleFailedIdsLocked(failedIds);
+ handleFailedIdsLocked(
+ failedIds, failedAutofillValues, hideHighlight, isRefill);
if (virtualValues != null) {
for (int i = 0; i < virtualValues.size(); i++) {
@@ -3322,7 +3455,7 @@
private void reportAutofillContentFailure(AutofillId id) {
try {
mService.setAutofillFailure(mSessionId, Collections.singletonList(id),
- mContext.getUserId());
+ false /* isRefill */, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3349,20 +3482,22 @@
}
/**
- * Set the tracked views.
+ * Set the tracked views.
*
- * @param trackedIds The views to be tracked.
+ * @param trackedIds The views to be tracked.
* @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
- * @param saveOnFinish Finish the session once the activity is finished.
- * @param fillableIds Views that might anchor FillUI.
- * @param saveTriggerId View that when clicked triggers commit().
+ * @param saveOnFinish Finish the session once the activity is finished.
+ * @param fillableIds Views that might anchor FillUI.
+ * @param saveTriggerId View that when clicked triggers commit().
*/
private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
- @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
+ @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId,
+ boolean shouldGrabViewFingerprints) {
if (saveTriggerId != null) {
saveTriggerId.resetSessionId();
}
+ final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
synchronized (mLock) {
if (sVerbose) {
Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId
@@ -3372,6 +3507,7 @@
+ ", fillableIds=" + Arrays.toString(fillableIds)
+ ", saveTrigerId=" + saveTriggerId
+ ", mFillableIds=" + mFillableIds
+ + ", shouldGrabViewFingerprints=" + shouldGrabViewFingerprints
+ ", mEnabled=" + mEnabled
+ ", mSessionId=" + mSessionId);
}
@@ -3405,7 +3541,6 @@
trackedIds = null;
}
- final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
if (mFillableIds != null) {
allFillableIds.addAll(mFillableIds);
}
@@ -3424,6 +3559,12 @@
mTrackedViews = null;
}
}
+ if (mRelayoutFix && shouldGrabViewFingerprints) {
+ // For all the views: tracked and others, calculate fingerprints and store them.
+ mAutofillStateFingerprint.setUseRelativePosition(mRelativePositionForRelayout);
+ mAutofillStateFingerprint.storeStatePriorToAuthentication(
+ getClient(), allFillableIds);
+ }
}
}
@@ -3845,7 +3986,7 @@
@GuardedBy("mLock")
private boolean isPendingAuthenticationLocked() {
- return mRelayoutFixDeprecated && mState == STATE_PENDING_AUTHENTICATION;
+ return (mRelayoutFixDeprecated || mRelayoutFix) && mState == STATE_PENDING_AUTHENTICATION;
}
@GuardedBy("mLock")
@@ -3858,7 +3999,7 @@
return mState == STATE_FINISHED;
}
- private void post(Runnable runnable) {
+ void post(Runnable runnable) {
final AutofillClient client = getClient();
if (client == null) {
if (sVerbose) Log.v(TAG, "ignoring post() because client is null");
@@ -4700,11 +4841,11 @@
@Override
public void setTrackedViews(int sessionId, AutofillId[] ids,
boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
- AutofillId saveTriggerId) {
+ AutofillId saveTriggerId, boolean shouldGrabViewFingerprints) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
- saveOnFinish, fillableIds, saveTriggerId));
+ saveOnFinish, fillableIds, saveTriggerId, shouldGrabViewFingerprints));
}
}
diff --git a/core/java/android/view/autofill/AutofillStateFingerprint.java b/core/java/android/view/autofill/AutofillStateFingerprint.java
new file mode 100644
index 0000000..2db4285
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillStateFingerprint.java
@@ -0,0 +1,352 @@
+/*
+ * 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.view.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class manages and stores the autofillable views fingerprints for use in relayout situations.
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public final class AutofillStateFingerprint {
+
+ ArrayList<AutofillId> mPriorAutofillIds;
+ ArrayList<Integer> mViewHashCodes; // each entry corresponding to mPriorAutofillIds .
+
+ boolean mHideHighlight = false;
+
+ private int mSessionId;
+
+ Map<Integer, AutofillId> mHashToAutofillIdMap = new ArrayMap<>();
+ Map<AutofillId, AutofillId> mOldIdsToCurrentAutofillIdMap = new ArrayMap<>();
+
+ // These failed id's are attempted to be refilled again after relayout.
+ private ArrayList<AutofillId> mFailedIds = new ArrayList<>();
+ private ArrayList<AutofillValue> mFailedAutofillValues = new ArrayList<>();
+
+ // whether to use relative positions for computing hashes.
+ private boolean mUseRelativePosition;
+
+ private static final String TAG = "AutofillStateFingerprint";
+
+ /**
+ * Returns an instance of this class
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static AutofillStateFingerprint createInstance() {
+ return new AutofillStateFingerprint();
+ }
+
+ private AutofillStateFingerprint() {
+ }
+
+ /**
+ * Set sessionId for the instance
+ */
+ void setSessionId(int sessionId) {
+ mSessionId = sessionId;
+ }
+
+ /**
+ * Sets whether relative position of the views should be used to calculate fingerprints.
+ */
+ void setUseRelativePosition(boolean useRelativePosition) {
+ mUseRelativePosition = useRelativePosition;
+ }
+
+ /**
+ * Store the state of the views prior to the authentication.
+ */
+ void storeStatePriorToAuthentication(
+ AutofillManager.AutofillClient client, Set<AutofillId> autofillIds) {
+ if (mUseRelativePosition) {
+ List<View> autofillableViews = client.autofillClientFindAutofillableViewsByTraversal();
+ if (sDebug) {
+ Log.d(TAG, "Autofillable views count prior to auth:" + autofillableViews.size());
+ }
+// ArrayList<Integer> hashes = getFingerprintIds(autofillableViews);
+
+ ArrayMap<Integer, View> hashes = getFingerprintIds(autofillableViews);
+ for (Map.Entry<Integer, View> entry : hashes.entrySet()) {
+ View view = entry.getValue();
+ if (view != null) {
+ mHashToAutofillIdMap.put(entry.getKey(), view.getAutofillId());
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Encountered null view");
+ }
+ }
+ }
+ } else {
+ // Just use the provided autofillIds and get their hashes
+ if (sDebug) {
+ Log.d(TAG, "Size of autofillId's being stored: " + autofillIds.size()
+ + " list:" + autofillIds);
+ }
+ AutofillId[] autofillIdsArr = Helper.toArray(autofillIds);
+ View[] views = client.autofillClientFindViewsByAutofillIdTraversal(autofillIdsArr);
+ for (int i = 0; i < autofillIdsArr.length; i++) {
+ View view = views[i];
+ if (view != null) {
+ int id = getEphemeralFingerprintId(view, 0 /* position irrelevant */);
+ AutofillId autofillId = view.getAutofillId();
+ autofillId.setSessionId(mSessionId);
+ mHashToAutofillIdMap.put(id, autofillId);
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Encountered null view");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Store failed ids, so that they can be refilled later
+ */
+ void storeFailedIdsAndValues(
+ @NonNull ArrayList<AutofillId> failedIds,
+ ArrayList<AutofillValue> failedAutofillValues,
+ boolean hideHighlight) {
+ for (AutofillId failedId : failedIds) {
+ if (failedId != null) {
+ failedId.setSessionId(mSessionId);
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Got null failed ids");
+ }
+ }
+ }
+ mFailedIds = failedIds;
+ mFailedAutofillValues = failedAutofillValues;
+ mHideHighlight = hideHighlight;
+ }
+
+ private void dumpCurrentState() {
+ Log.d(TAG, "FailedId's: " + mFailedIds);
+ Log.d(TAG, "Hashes from map" + mHashToAutofillIdMap);
+ }
+
+ boolean attemptRefill(
+ List<View> currentAutofillableViews, @NonNull AutofillManager autofillManager) {
+ if (sDebug) {
+ dumpCurrentState();
+ }
+ // For the autofillable views, compute their hashes
+ ArrayMap<Integer, View> currentHashes = getFingerprintIds(currentAutofillableViews);
+
+ // For the computed hashes, try to look for the old fingerprints.
+ // If match found, update the new autofill ids of those views
+ Map<AutofillId, View> oldFailedIdsToCurrentViewMap = new HashMap<>();
+ for (Map.Entry<Integer, View> entry : currentHashes.entrySet()) {
+ View view = entry.getValue();
+ int currentHash = entry.getKey();
+ AutofillId currentAutofillId = view.getAutofillId();
+ currentAutofillId.setSessionId(mSessionId);
+ if (mHashToAutofillIdMap.containsKey(currentHash)) {
+ AutofillId oldAutofillId = mHashToAutofillIdMap.get(currentHash);
+ oldAutofillId.setSessionId(mSessionId);
+ mOldIdsToCurrentAutofillIdMap.put(oldAutofillId, currentAutofillId);
+ Log.i(TAG, "Mapping current autofill id: " + view.getAutofillId()
+ + " to existing autofill id " + oldAutofillId);
+
+ oldFailedIdsToCurrentViewMap.put(oldAutofillId, view);
+ } else {
+ Log.i(TAG, "Couldn't map current autofill id: " + view.getAutofillId()
+ + " with currentHash:" + currentHash + " for view:" + view);
+ }
+ }
+
+ int viewsCount = 0;
+ View[] views = new View[mFailedIds.size()];
+ for (int i = 0; i < mFailedIds.size(); i++) {
+ AutofillId oldAutofillId = mFailedIds.get(i);
+ AutofillId currentAutofillId = mOldIdsToCurrentAutofillIdMap.get(oldAutofillId);
+ if (currentAutofillId == null) {
+ if (sDebug) {
+ Log.d(TAG, "currentAutofillId = null");
+ }
+ }
+ mFailedIds.set(i, currentAutofillId);
+ views[i] = oldFailedIdsToCurrentViewMap.get(oldAutofillId);
+ if (views[i] != null) {
+ viewsCount++;
+ }
+ }
+
+ if (sDebug) {
+ dumpCurrentState();
+ }
+
+ // Attempt autofill now
+ Slog.i(TAG, "Attempting refill of views. Found " + viewsCount
+ + " views to refill from previously " + mFailedIds.size()
+ + " failed ids:" + mFailedIds);
+ autofillManager.post(
+ () -> autofillManager.autofill(
+ views, mFailedIds, mFailedAutofillValues, mHideHighlight,
+ true /* isRefill */));
+
+ return false;
+ }
+
+ /**
+ * Retrieves fingerprint hashes for the views
+ */
+ ArrayMap<Integer, View> getFingerprintIds(@NonNull List<View> views) {
+ ArrayMap<Integer, View> map = new ArrayMap<>();
+ if (mUseRelativePosition) {
+ Collections.sort(views, (View v1, View v2) -> {
+ int[] posV1 = v1.getLocationOnScreen();
+ int[] posV2 = v2.getLocationOnScreen();
+
+ int compare = posV1[0] - posV2[0]; // x coordinate
+ if (compare != 0) {
+ return compare;
+ }
+ compare = posV1[1] - posV2[1]; // y coordinate
+ if (compare != 0) {
+ return compare;
+ }
+ // Sort on vertical
+ compare = compareTop(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ compare = compareBottom(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ compare = compareLeft(v1, v2);
+ if (compare != 0) {
+ return compare;
+ }
+ return compareRight(v1, v2);
+ // Note that if compareRight also returned 0, that means both the views have exact
+ // same location, so just treat them as equal
+ });
+ }
+ for (int i = 0; i < views.size(); i++) {
+ View view = views.get(i);
+ map.put(getEphemeralFingerprintId(view, i), view);
+ }
+ return map;
+ }
+
+ /**
+ * Returns fingerprint hash for the view.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public int getEphemeralFingerprintId(View v, int position) {
+ if (v == null) return -1;
+ int inputType = Integer.MIN_VALUE;
+ int imeOptions = Integer.MIN_VALUE;
+ boolean isSingleLine = false;
+ CharSequence hints = "";
+ if (v instanceof TextView) {
+ TextView tv = (TextView) v;
+ inputType = tv.getInputType();
+ hints = tv.getHint();
+ isSingleLine = tv.isSingleLine();
+ imeOptions = tv.getImeOptions();
+ // TODO(b/238252288): Consider adding more IME related fields.
+ }
+ CharSequence contentDesc = v.getContentDescription();
+ CharSequence tooltip = v.getTooltipText();
+
+ int autofillType = v.getAutofillType();
+ String[] autofillHints = v.getAutofillHints();
+ int visibility = v.getVisibility();
+
+ int paddingLeft = v.getPaddingLeft();
+ int paddingRight = v.getPaddingRight();
+ int paddingTop = v.getPaddingTop();
+ int paddingBottom = v.getPaddingBottom();
+
+ // TODO(b/238252288): Following are making relayout flaky. Do more analysis to figure out
+ // why.
+ int height = v.getHeight();
+ int width = v.getWidth();
+
+ // Order doesn't matter much here. We can change the order, as long as we use the same
+ // order for storing and fetching fingerprints. The order can be changed in platform
+ // versions.
+ int hash = Objects.hash(visibility, inputType, imeOptions, isSingleLine, hints,
+ contentDesc, tooltip, autofillType, Arrays.deepHashCode(autofillHints),
+ paddingBottom, paddingTop, paddingRight, paddingLeft);
+ if (mUseRelativePosition) {
+ hash = Objects.hash(hash, position);
+ }
+ if (sDebug) {
+ Log.d(TAG, "Hash: " + hash + " for AutofillId:" + v.getAutofillId()
+ + " visibility:" + visibility
+ + " inputType:" + inputType
+ + " imeOptions:" + imeOptions
+ + " isSingleLine:" + isSingleLine
+ + " hints:" + hints
+ + " contentDesc:" + contentDesc
+ + " tooltipText:" + tooltip
+ + " autofillType:" + autofillType
+ + " autofillHints:" + Arrays.toString(autofillHints)
+ + " height:" + height
+ + " width:" + width
+ + " paddingLeft:" + paddingLeft
+ + " paddingRight:" + paddingRight
+ + " paddingTop:" + paddingTop
+ + " paddingBottom:" + paddingBottom
+ + " mUseRelativePosition" + mUseRelativePosition
+ + " position:" + position
+ );
+ }
+ return hash;
+ }
+
+ private int compareTop(View v1, View v2) {
+ return v1.getTop() - v2.getTop();
+ }
+
+ private int compareBottom(View v1, View v2) {
+ return v1.getBottom() - v2.getBottom();
+ }
+
+ private int compareLeft(View v1, View v2) {
+ return v1.getLeft() - v2.getLeft();
+ }
+
+ private int compareRight(View v1, View v2) {
+ return v1.getRight() - v2.getRight();
+ }
+}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 2039b4d..f67405f 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -48,7 +48,7 @@
in IResultReceiver result);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
- void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void setAutofillFailure(int sessionId, in List<AutofillId> ids, boolean isRefill, int userId);
void setViewAutofilled(int sessionId, in AutofillId id, int userId);
void finishSession(int sessionId, int userId, int commitReason);
void cancelSession(int sessionId, int userId);
@@ -67,4 +67,7 @@
void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
void setAugmentedAutofillWhitelist(in List<String> packages, in List<ComponentName> activities,
in IResultReceiver result);
+ void notifyNotExpiringResponseDuringAuth(int sessionId, int userId);
+ void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int userId);
+ void setAutofillIdsAttemptedForRefill(int sessionId, in List<AutofillId> ids, int userId);
}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 904a7e0..39d71da 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -72,7 +72,8 @@
*/
void setTrackedViews(int sessionId, in @nullable AutofillId[] savableIds,
boolean saveOnAllViewsInvisible, boolean saveOnFinish,
- in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId);
+ in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId,
+ in boolean shouldGrabViewFingerprints);
/**
* Requests showing the fill UI.
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index 3f3d6a3..4e3b2af 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -157,6 +157,21 @@
assertThat(mCurrentConnection.takePid(), is(NOT_STARTED));
}
+ @Test
+ public void testRestart_stickyStartedService_unbindHappenedAfterRestart_restarted() {
+ final int servicePid = startService(Service.START_STICKY);
+ assertThat(servicePid, not(NOT_STARTED));
+ assertThat(bindService(0 /* flags */), is(servicePid));
+
+ final int restartedServicePid = waitForServiceStarted(
+ () -> {
+ Process.killProcess(servicePid);
+ mContext.unbindService(mCurrentConnection);
+ mCurrentConnection = null;
+ });
+ assertThat(restartedServicePid, not(NOT_STARTED));
+ }
+
/** @return The pid of the started service. */
private int startService(int code) {
return waitForServiceStarted(
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 18364ad..b8ff595 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -278,7 +278,7 @@
@RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
- public void lowVelocity80() throws Throwable {
+ public void lowVelocity60() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
}
@@ -292,6 +292,31 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.setFrameContentVelocity(1f);
mMovingView.invalidate();
+ runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ });
+ waitForAfterDraw();
+ }
+
+ @Test
+ @RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
+ FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
+ public void midVelocity80() throws Throwable {
+ if (!ViewProperties.vrr_enabled().orElse(true)) {
+ return;
+ }
+ mActivityRule.runOnUiThread(() -> {
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mMovingView.setLayoutParams(layoutParams);
+ });
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> {
+ float midSpeed =
+ 200f * mMovingView.getContext().getResources().getDisplayMetrics().density;
+ mMovingView.setFrameContentVelocity(midSpeed);
+ mMovingView.invalidate();
runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
@@ -321,7 +346,7 @@
frameLayout.setFrameContentVelocity(1f);
mMovingView.offsetTopAndBottom(100);
frameLayout.invalidate();
- runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
}
@@ -590,7 +615,7 @@
runAfterDraw(() -> {
assertEquals(FRAME_RATE_CATEGORY_LOW,
mViewRoot.getLastPreferredFrameRateCategory());
- assertEquals(80f, mViewRoot.getLastPreferredFrameRate());
+ assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
});
});
waitForAfterDraw();
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java b/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java
new file mode 100644
index 0000000..7cbfc40
--- /dev/null
+++ b/core/tests/coretests/src/android/view/autofill/AutofillStateFingerprintTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.view.autofill;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Context;
+import android.text.InputType;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutofillStateFingerprintTest {
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ private static final int MAGIC_AUTOFILL_NUMBER = 1000;
+
+ private AutofillStateFingerprint mAutofillStateFingerprint =
+ AutofillStateFingerprint.createInstance();
+
+ @Test
+ public void testSameFingerprintsForTextView() throws Exception {
+ TextView tv = new TextView(sContext);
+ tv.setHint("Password");
+ tv.setSingleLine(true);
+ tv.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ tv.setImeOptions(EditorInfo.IME_FLAG_NAVIGATE_NEXT);
+ fillViewProperties(tv);
+
+ // Create a copy Text View, and compare both id's
+ View tvCopy = copySelectiveViewAttributes(tv);
+ assertIdsEqual(tv, tvCopy);
+ }
+
+ @Test
+ public void testDifferentFingerprintsForTextViewWithDifferentHint() throws Exception {
+ TextView tv = new TextView(sContext);
+ tv.setHint("Password");
+ tv.setSingleLine(true);
+ tv.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ tv.setImeOptions(EditorInfo.IME_FLAG_NAVIGATE_NEXT);
+ fillViewProperties(tv);
+
+ TextView tvCopy = (TextView) copySelectiveViewAttributes(tv);
+ tvCopy.setHint("what a useless different hint");
+ assertIdsNotEqual(tv, tvCopy);
+ }
+
+ @Test
+ public void testSameFingerprintsForNonTextView() throws Exception {
+ View v = new View(sContext);
+ fillViewProperties(v);
+
+ // Create a copy Text View, and compare both id's
+ View copy = copySelectiveViewAttributes(v);
+ assertIdsEqual(v, copy);
+ }
+
+ @Test
+ public void testDifferentFingerprintsForNonTextViewWithDifferentVisibility() throws Exception {
+ View v = new View(sContext);
+ fillViewProperties(v);
+
+ View copy = copySelectiveViewAttributes(v);
+ copy.setVisibility(View.GONE);
+ assertIdsNotEqual(v, copy);
+ }
+
+ private void assertIdsEqual(View v1, View v2) {
+ assertEquals(mAutofillStateFingerprint.getEphemeralFingerprintId(v1, 0),
+ mAutofillStateFingerprint.getEphemeralFingerprintId(v2, 0));
+ }
+
+ private void assertIdsNotEqual(View v1, View v2) {
+ assertNotEquals(mAutofillStateFingerprint.getEphemeralFingerprintId(v1, 0),
+ mAutofillStateFingerprint.getEphemeralFingerprintId(v2, 0));
+ }
+
+ private void fillViewProperties(View view) {
+ // Fill in relevant view properties
+ view.setContentDescription("ContentDesc");
+ view.setTooltipText("TooltipText");
+ view.setAutofillHints(new String[] {"password"});
+ view.setVisibility(View.VISIBLE);
+ view.setLeft(20);
+ view.setRight(200);
+ view.setTop(20);
+ view.setBottom(200);
+ view.setPadding(0, 1, 2, 3);
+ }
+
+ // Only copy interesting view attributes, particularly the view attributes that are critical
+ // for calculating fingerprint. Keep Autofill Id different.
+ private View copySelectiveViewAttributes(View view) {
+ View copy;
+ if (view instanceof TextView) {
+ copy = new TextView(sContext);
+ copySelectiveTextViewAttributes((TextView) view, (TextView) copy);
+ } else {
+ copy = new View(sContext) {
+ public @AutofillType int getAutofillType() {
+ return view.getAutofillType();
+ }
+ };
+ }
+ // Copy over interested view properties.
+ // Keep the order same as with the tested code for easier clarity.
+ copy.setVisibility(view.getVisibility());
+ copy.setAutofillHints(view.getAutofillHints());
+ copy.setContentDescription(view.getContentDescription());
+ copy.setTooltip(view.getTooltipText());
+
+ copy.setRight(view.getRight());
+ copy.setLeft(view.getLeft());
+ copy.setTop(view.getTop());
+ copy.setBottom(view.getBottom());
+ copy.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+ view.getPaddingRight(), view.getPaddingBottom());
+
+ // DO not copy over autofill id
+ AutofillId newId = new AutofillId(view.getAutofillId().getViewId() + MAGIC_AUTOFILL_NUMBER);
+ copy.setAutofillId(newId);
+ return copy;
+ }
+
+ private void copySelectiveTextViewAttributes(TextView fromView, TextView toView) {
+ toView.setInputType(fromView.getInputType());
+ toView.setHint(fromView.getHint());
+ toView.setSingleLine(fromView.isSingleLine());
+ toView.setImeOptions(fromView.getImeOptions());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 972dce5..24c568c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -169,6 +169,8 @@
R.layout.bubble_overflow_container, null /* root */);
mOverflowView.initialize(expandedViewManager, positioner);
addView(mOverflowView);
+ // Don't show handle for overflow
+ mHandleView.setVisibility(View.GONE);
} else {
mTaskView = bubbleTaskView.getTaskView();
mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index afe46f5..35d3876 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -569,7 +569,7 @@
ShellTaskOrganizer shellTaskOrganizer) {
int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context);
if (!DesktopModeStatus.canEnterDesktopMode(context)
- || DESKTOP_WINDOWING_MODE.isEnabled(context)
+ || !DESKTOP_WINDOWING_MODE.isEnabled(context)
|| maxTaskLimit <= 0) {
return Optional.empty();
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index b4a9172..ce02404 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -129,7 +129,7 @@
}
}
- override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+ override fun onChanged(observable: Observable, reason: Int) = onKeyChanged(null, reason)
override fun onKeyChanged(key: Any?, reason: Int) {
notifyBackupManager(key, reason)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index ede7c63..4ce1d37 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -103,7 +103,7 @@
}
/** A thread safe implementation of [KeyedObservable]. */
-class KeyedDataObservable<K> : KeyedObservable<K> {
+open class KeyedDataObservable<K> : KeyedObservable<K> {
// Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be
// synchronized outside by the holder
@GuardedBy("itself") private val observers = WeakHashMap<KeyedObserver<K?>, Executor>()
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
index 0e399c0..300d240 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/ObservableBackupRestoreStorage.kt
@@ -21,5 +21,7 @@
*
* This class provides the [Observable] implementations on top of [DataObservable] by delegation.
*/
-abstract class ObservableBackupRestoreStorage :
- BackupRestoreStorage(), Observable by DataObservable()
+abstract class ObservableBackupRestoreStorage : BackupRestoreStorage(), ObservableDelegation {
+
+ final override val observableDelegate: Observable = DataObservable(this)
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt
index 98d0f6e..6af3d1c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt
@@ -32,10 +32,11 @@
*
* This callback will run in the given [Executor] when observer is added.
*
+ * @param observable observable of the change
* @param reason the reason of change
* @see [Observable.addObserver] for the notices.
*/
- fun onChanged(reason: Int)
+ fun onChanged(observable: Observable, reason: Int)
}
/** An observable object allows to observe change with [Observer]. */
@@ -68,8 +69,21 @@
fun notifyChange(reason: Int)
}
+/** Delegation of [Observable]. */
+interface ObservableDelegation : Observable {
+ /** [Observable] to delegate. */
+ val observableDelegate: Observable
+
+ override fun addObserver(observer: Observer, executor: Executor) =
+ observableDelegate.addObserver(observer, executor)
+
+ override fun removeObserver(observer: Observer) = observableDelegate.removeObserver(observer)
+
+ override fun notifyChange(reason: Int) = observableDelegate.notifyChange(reason)
+}
+
/** A thread safe implementation of [Observable]. */
-class DataObservable : Observable {
+class DataObservable(private val observable: Observable) : Observable {
// Instead of @GuardedBy("this"), guarded by itself because DataObservable object could be
// synchronized outside by the holder
@GuardedBy("itself") private val observers = WeakHashMap<Observer, Executor>()
@@ -90,7 +104,7 @@
val entries = synchronized(observers) { observers.entries.toTypedArray() }
for (entry in entries) {
val observer = entry.key // avoid reference "entry"
- entry.value.execute { observer.onChanged(reason) }
+ entry.value.execute { observer.onChanged(observable, reason) }
}
}
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.kt
new file mode 100644
index 0000000..e70ec5b
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesObservable.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.settingslib.datastore
+
+import android.content.SharedPreferences
+
+/** [SharedPreferences] based [KeyedDataObservable]. */
+class SharedPreferencesObservable(private val sharedPreferences: SharedPreferences) :
+ KeyedDataObservable<String>(), AutoCloseable {
+
+ private val listener = createSharedPreferenceListener()
+
+ init {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ }
+
+ override fun close() {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
+ }
+}
+
+/** Creates [SharedPreferences.OnSharedPreferenceChangeListener] for [KeyedObservable]. */
+internal fun KeyedObservable<String>.createSharedPreferenceListener() =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ if (key != null) {
+ notifyChange(key, DataChangeReason.UPDATE)
+ } else {
+ // On Android >= R, SharedPreferences.Editor.clear() will trigger this case
+ notifyChange(DataChangeReason.DELETE)
+ }
+ }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
index 20a95d7..0ca91cd 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
@@ -80,15 +80,7 @@
return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS)
}
- private val sharedPreferencesListener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
- if (key != null) {
- notifyChange(key, DataChangeReason.UPDATE)
- } else {
- // On Android >= R, SharedPreferences.Editor.clear() will trigger this case
- notifyChange(DataChangeReason.DELETE)
- }
- }
+ private val sharedPreferencesListener = createSharedPreferenceListener()
init {
// listener is weakly referenced, so unregister is optional
@@ -191,8 +183,7 @@
else -> {
Log.e(
LOG_TAG,
- "[$name] $operation $key=$value, unknown type: ${value?.javaClass}"
- )
+ "[$name] $operation $key=$value, unknown type: ${value?.javaClass}")
}
}
}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt
index 19c574a..97b473c 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt
@@ -159,7 +159,7 @@
verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE)
verify(anyKeyObserver).onKeyChanged(null, DataChangeReason.RESTORE)
- verify(observer).onChanged(DataChangeReason.RESTORE)
+ verify(observer).onChanged(fileStorage, DataChangeReason.RESTORE)
if (isRobolectric()) {
Shadows.shadowOf(BackupManager(application)).apply {
assertThat(isDataChanged).isFalse()
@@ -187,7 +187,7 @@
}
fileStorage.notifyChange(DataChangeReason.UPDATE)
- verify(observer).onChanged(DataChangeReason.UPDATE)
+ verify(observer).onChanged(fileStorage, DataChangeReason.UPDATE)
verify(keyedObserver, never()).onKeyChanged(any(), any())
verify(anyKeyObserver, never()).onKeyChanged(any(), any())
reset(observer)
@@ -197,7 +197,7 @@
}
keyedStorage.notifyChange("key", DataChangeReason.DELETE)
- verify(observer, never()).onChanged(any())
+ verify(observer, never()).onChanged(any(), any())
verify(keyedObserver).onKeyChanged("key", DataChangeReason.DELETE)
verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.DELETE)
backupManager?.apply {
@@ -209,7 +209,7 @@
// backup manager is not notified for restore event
fileStorage.notifyChange(DataChangeReason.RESTORE)
keyedStorage.notifyChange("key", DataChangeReason.RESTORE)
- verify(observer).onChanged(DataChangeReason.RESTORE)
+ verify(observer).onChanged(fileStorage, DataChangeReason.RESTORE)
verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE)
verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.RESTORE)
backupManager?.apply {
@@ -225,7 +225,10 @@
}
private class FileStorage(override val name: String) :
- BackupRestoreFileStorage(getApplicationContext(), "file"), Observable by DataObservable()
+ BackupRestoreFileStorage(getApplicationContext(), "file"), ObservableDelegation {
+
+ override val observableDelegate: Observable = DataObservable(this)
+ }
private class DummyBackupAgentHelper : BackupAgentHelper() {
val backupHelpers = mutableMapOf<String, BackupHelper>()
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
index 5d0303c..bd114d1 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -24,6 +24,7 @@
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
@@ -33,10 +34,11 @@
class ObserverTest {
private val observer1 = mock<Observer>()
private val observer2 = mock<Observer>()
+ private val originalObservable = mock<Observable>()
private val executor1: Executor = MoreExecutors.directExecutor()
private val executor2: Executor = MoreExecutors.newDirectExecutorService()
- private val observable = DataObservable()
+ private val observable = DataObservable(originalObservable)
@Test
fun addObserver_sameExecutor() {
@@ -55,7 +57,7 @@
@Test
fun addObserver_weaklyReferenced() {
val counter = AtomicInteger()
- var observer: Observer? = Observer { counter.incrementAndGet() }
+ var observer: Observer? = Observer { _, _ -> counter.incrementAndGet() }
observable.addObserver(observer!!, executor1)
observable.notifyChange(DataChangeReason.UPDATE)
@@ -77,21 +79,21 @@
observable.notifyChange(DataChangeReason.DELETE)
- verify(observer1).onChanged(DataChangeReason.DELETE)
- verify(observer2).onChanged(DataChangeReason.DELETE)
+ verify(observer1).onChanged(originalObservable, DataChangeReason.DELETE)
+ verify(observer2).onChanged(originalObservable, DataChangeReason.DELETE)
reset(observer1, observer2)
observable.removeObserver(observer2)
observable.notifyChange(DataChangeReason.UPDATE)
- verify(observer1).onChanged(DataChangeReason.UPDATE)
- verify(observer2, never()).onChanged(DataChangeReason.UPDATE)
+ verify(observer1).onChanged(originalObservable, DataChangeReason.UPDATE)
+ verify(observer2, never()).onChanged(any(), any())
}
@Test
fun notifyChange_addObserverWithinCallback() {
// ConcurrentModificationException is raised if it is not implemented correctly
- val observer = Observer { observable.addObserver(observer1, executor1) }
+ val observer = Observer { _, _ -> observable.addObserver(observer1, executor1) }
observable.addObserver(observer, executor1)
observable.notifyChange(DataChangeReason.UPDATE)
observable.removeObserver(observer)
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 990a2d4..732b358 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -16,6 +16,8 @@
package com.android.settingslib.notification.modes;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
@@ -298,6 +300,17 @@
return mIsManualDnd;
}
+ /**
+ * A <em>custom manual</em> mode is a mode created by the user, and not yet assigned an
+ * automatic trigger condition (neither time schedule nor a calendar).
+ */
+ public boolean isCustomManual() {
+ return isSystemOwned()
+ && getType() != TYPE_SCHEDULE_TIME
+ && getType() != TYPE_SCHEDULE_CALENDAR
+ && !isManualDnd();
+ }
+
public boolean isEnabled() {
return mRule.isEnabled();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index e705f97..651e57c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -27,6 +27,7 @@
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
+import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
@@ -109,6 +110,61 @@
}
@Test
+ public void isCustomManual_customManualMode() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .build();
+ ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
+
+ assertThat(mode.isCustomManual()).isTrue();
+ }
+
+ @Test
+ public void isCustomManual_scheduleTime_false() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
+ .build();
+ ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
+
+ assertThat(mode.isCustomManual()).isFalse();
+ }
+
+ @Test
+ public void isCustomManual_scheduleCalendar_false() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR)
+ .build();
+ ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
+
+ assertThat(mode.isCustomManual()).isFalse();
+ }
+
+ @Test
+ public void isCustomManual_appProvidedMode_false() {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
+ .setPackage("com.some.package")
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .build();
+ ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
+
+ assertThat(mode.isCustomManual()).isFalse();
+ }
+
+ @Test
+ public void isCustomManual_manualDnd_false() {
+ AutomaticZenRule dndRule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setType(AutomaticZenRule.TYPE_OTHER)
+ .build();
+ ZenMode mode = ZenMode.manualDndMode(dndRule, false);
+
+ assertThat(mode.isCustomManual()).isFalse();
+ }
+
+ @Test
public void getPolicy_interruptionFilterPriority_returnsZenPolicy() {
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2b8b23e..40a8199 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -253,6 +253,7 @@
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
Settings.Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED,
+ Settings.Secure.HINGE_ANGLE_LIDEVENT_ENABLED,
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
Settings.Secure.HEARING_AID_CALL_ROUTING,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cc5302b..3b9c683 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -406,6 +406,7 @@
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.HINGE_ANGLE_LIDEVENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HEARING_AID_RINGTONE_ROUTING,
new DiscreteValueValidator(new String[] {"0", "1", "2"}));
VALIDATORS.put(Secure.HEARING_AID_CALL_ROUTING,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 8245cc5..3be5231 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -7,6 +7,7 @@
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
@@ -25,6 +26,9 @@
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Back
@@ -41,6 +45,7 @@
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.internal.R.attr.focusable
import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -207,6 +212,8 @@
backgroundType = backgroundType,
colors = colors,
content = content,
+ viewModel = viewModel,
+ modifier = Modifier.horizontalNestedScrollToScene(),
)
}
}
@@ -222,17 +229,41 @@
backgroundType: CommunalBackgroundType,
colors: CommunalColors,
content: CommunalContent,
+ viewModel: CommunalViewModel,
modifier: Modifier = Modifier,
) {
- Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) {
+ val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
+
+ Box(
+ modifier =
+ Modifier.element(Communal.Elements.Scrim)
+ .fillMaxSize()
+ .then(
+ if (isFocusable) {
+ Modifier.focusable()
+ } else {
+ Modifier.semantics { contentDescription = "" }.clearAndSetSemantics {}
+ }
+ )
+ ) {
when (backgroundType) {
CommunalBackgroundType.STATIC -> DefaultBackground(colors = colors)
CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient()
CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient()
CommunalBackgroundType.NONE -> BackgroundTopScrim()
}
+
+ with(content) {
+ Content(
+ modifier =
+ modifier.focusable(isFocusable).semantics {
+ if (!isFocusable) {
+ contentDescription = ""
+ }
+ }
+ )
+ }
}
- with(content) { Content(modifier = modifier) }
}
/** Default background of the hub, a single color */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e29e0fd..b759cf7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -221,7 +221,7 @@
val layoutDirection = LocalLayoutDirection.current
if (viewModel.isEditMode) {
- ScrollOnNewWidgetAddedEffect(communalContent, gridState)
+ ObserveNewWidgetAddedEffect(communalContent, gridState, viewModel)
} else {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
@@ -553,19 +553,37 @@
}
}
-/** Observes communal content and scrolls to a newly added widget if any. */
+/**
+ * Observes communal content and determines whether a new widget has been added, upon which case:
+ * - Announce for accessibility
+ * - Scroll if the new widget is not visible
+ */
@Composable
-private fun ScrollOnNewWidgetAddedEffect(
+private fun ObserveNewWidgetAddedEffect(
communalContent: List<CommunalContentModel>,
gridState: LazyGridState,
+ viewModel: BaseCommunalViewModel,
) {
val coroutineScope = rememberCoroutineScope()
val widgetKeys = remember { mutableListOf<String>() }
+ var communalContentPending by remember { mutableStateOf(true) }
LaunchedEffect(communalContent) {
+ // Do nothing until any communal content comes in
+ if (communalContentPending && communalContent.isEmpty()) {
+ return@LaunchedEffect
+ }
+
val oldWidgetKeys = widgetKeys.toList()
+ val widgets = communalContent.filterIsInstance<CommunalContentModel.WidgetContent.Widget>()
widgetKeys.clear()
- widgetKeys.addAll(communalContent.filter { it.isWidgetContent() }.map { it.key })
+ widgetKeys.addAll(widgets.map { it.key })
+
+ // Do nothing on first communal content since we don't have a delta
+ if (communalContentPending) {
+ communalContentPending = false
+ return@LaunchedEffect
+ }
// Do nothing if there is no new widget
val indexOfFirstNewWidget = widgetKeys.indexOfFirst { !oldWidgetKeys.contains(it) }
@@ -573,6 +591,8 @@
return@LaunchedEffect
}
+ viewModel.onNewWidgetAdded(widgets[indexOfFirstNewWidget].providerInfo)
+
// Scroll if the new widget is not visible
val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
if (lastVisibleItemIndex != null && indexOfFirstNewWidget > lastVisibleItemIndex) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 9e857deb..5eca5b4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -18,9 +18,11 @@
import android.provider.Settings
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -33,6 +35,7 @@
private val scope: CoroutineScope,
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
+ private val systemSettingsRepository: SystemSettingsRepository,
) {
val isNotificationHistoryEnabled: Flow<Boolean> =
secureSettingsRepository
@@ -60,4 +63,15 @@
)
}
}
+
+ val isCooldownEnabled: StateFlow<Boolean> =
+ systemSettingsRepository
+ .intSetting(name = Settings.System.NOTIFICATION_COOLDOWN_ENABLED)
+ .map { it == 1 }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index b4105bd..a274f1d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -38,4 +38,5 @@
val current = repository.isShowNotificationsOnLockScreenEnabled().value
repository.setShowNotificationsOnLockscreenEnabled(!current)
}
-}
+
+ val isCooldownEnabled = repository.isCooldownEnabled}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
new file mode 100644
index 0000000..afe82fb
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Defines interface for classes that can provide access to data from [Settings.System]. This
+ * repository doesn't guarantee to provide value across different users. For that see:
+ * [UserAwareSecureSettingsRepository] which does that for secure settings.
+ */
+interface SystemSettingsRepository {
+
+ /** Returns a [Flow] tracking the value of a setting as an [Int]. */
+ fun intSetting(
+ name: String,
+ defaultValue: Int = 0,
+ ): Flow<Int>
+
+ /** Updates the value of the setting with the given name. */
+ suspend fun setInt(
+ name: String,
+ value: Int,
+ )
+
+ suspend fun getInt(
+ name: String,
+ defaultValue: Int = 0,
+ ): Int
+
+ suspend fun getString(name: String): String?
+}
+
+class SystemSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SystemSettingsRepository {
+
+ override fun intSetting(
+ name: String,
+ defaultValue: Int,
+ ): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.System.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.System.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ withContext(backgroundDispatcher) {
+ Settings.System.putInt(
+ contentResolver,
+ name,
+ value,
+ )
+ }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getInt(
+ contentResolver,
+ name,
+ defaultValue,
+ )
+ }
+ }
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt
new file mode 100644
index 0000000..7da2b40
--- /dev/null
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.settings.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeSystemSettingsRepository : SystemSettingsRepository {
+
+ private val settings = MutableStateFlow<Map<String, String>>(mutableMapOf())
+
+ override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return settings.value[name]?.toInt() ?: defaultValue
+ }
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 61487b0..57ce9de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.view.viewmodel
+import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
@@ -24,6 +25,9 @@
import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
import android.provider.Settings
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
import androidx.activity.result.ActivityResultLauncher
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,7 +46,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -61,8 +64,6 @@
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
@@ -77,8 +78,12 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -98,6 +103,7 @@
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var communalSceneInteractor: CommunalSceneInteractor
private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var accessibilityManager: AccessibilityManager
private val testableResources = context.orCreateTestableResources
@@ -119,6 +125,7 @@
selectedUserIndex = 0,
)
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ accessibilityManager = kosmos.accessibilityManager
underTest =
CommunalEditModeViewModel(
@@ -130,8 +137,10 @@
uiEventLogger,
logcatLogBuffer("CommunalEditModeViewModelTest"),
kosmos.testDispatcher,
- kosmos.communalPrefsInteractor,
metricsLogger,
+ context,
+ accessibilityManager,
+ packageManager,
)
}
@@ -356,6 +365,37 @@
verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
}
+ @Test
+ fun onNewWidgetAdded_accessibilityDisabled_doNothing() {
+ whenever(accessibilityManager.isEnabled).thenReturn(false)
+
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
+
+ verify(accessibilityManager, never()).sendAccessibilityEvent(any())
+ }
+
+ @Test
+ fun onNewWidgetAdded_accessibilityEnabled_sendAccessibilityAnnouncement() {
+ whenever(accessibilityManager.isEnabled).thenReturn(true)
+
+ val provider =
+ mock<AppWidgetProviderInfo> {
+ on { loadLabel(packageManager) }.thenReturn("Test Clock")
+ }
+ underTest.onNewWidgetAdded(provider)
+
+ val captor = argumentCaptor<AccessibilityEvent>()
+ verify(accessibilityManager).sendAccessibilityEvent(captor.capture())
+
+ val event = captor.firstValue
+ assertThat(event.eventType).isEqualTo(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+ assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen")
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 4c24ce2..5c09777 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -62,12 +62,13 @@
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
import com.android.systemui.dreams.dagger.DreamOverlayComponent
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.gesture.domain.gestureInteractor
import com.android.systemui.kosmos.testScope
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -84,9 +85,11 @@
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
-import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -166,6 +169,7 @@
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
+ private lateinit var gestureInteractor: GestureInteractor
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
@@ -177,6 +181,7 @@
lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
bouncerRepository = kosmos.fakeKeyguardBouncerRepository
communalRepository = kosmos.fakeCommunalSceneRepository
+ gestureInteractor = spy(kosmos.gestureInteractor)
whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController)
@@ -231,6 +236,7 @@
HOME_CONTROL_PANEL_DREAM_COMPONENT,
mDreamOverlayCallbackController,
kosmos.keyguardInteractor,
+ gestureInteractor,
WINDOW_NAME
)
}
@@ -955,6 +961,47 @@
assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED)
}
+ @Test
+ fun testDreamActivityGesturesBlockedOnStart() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureInteractor)
+ .addGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
+ assertThat(captor.firstValue.packageName)
+ .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
+ }
+
+ @Test
+ fun testDreamActivityGesturesUnblockedOnEnd() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+
+ client.endDream()
+ mMainExecutor.runAllReady()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureInteractor)
+ .removeGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
+ assertThat(captor.firstValue.packageName)
+ .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
+ }
+
internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
val mLifecycles: MutableList<State> = ArrayList()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
new file mode 100644
index 0000000..91d37cf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.gesture.data
+
+import android.content.ComponentName
+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.navigationbar.gestural.data.respository.GestureRepositoryImpl
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GestureRepositoryTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val underTest by lazy { GestureRepositoryImpl(testDispatcher) }
+
+ @Test
+ fun addRemoveComponentToBlock_updatesBlockedComponentSet() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+
+ underTest.addGestureBlockedActivity(component)
+ val addedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
+ assertThat(addedBlockedComponents).contains(component)
+
+ underTest.removeGestureBlockedActivity(component)
+ val removedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
+ assertThat(removedBlockedComponents).isEmpty()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
new file mode 100644
index 0000000..bc142e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.gesture.domain
+
+import android.content.ComponentName
+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.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GestureInteractorTest : SysuiTestCase() {
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ val dispatcher = StandardTestDispatcher()
+ val testScope = TestScope(dispatcher)
+
+ @Mock private lateinit var gestureRepository: GestureRepository
+
+ private val underTest by lazy {
+ GestureInteractor(gestureRepository, testScope.backgroundScope)
+ }
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(dispatcher)
+ whenever(gestureRepository.gestureBlockedActivities).thenReturn(MutableStateFlow(setOf()))
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun addBlockedActivity_testCombination() =
+ testScope.runTest {
+ val globalComponent = mock<ComponentName>()
+ whenever(gestureRepository.gestureBlockedActivities)
+ .thenReturn(MutableStateFlow(setOf(globalComponent)))
+ val localComponent = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).addGestureBlockedActivity(any())
+ assertThat(lastSeen).hasSize(2)
+ assertThat(lastSeen).containsExactly(globalComponent, localComponent)
+ }
+
+ @Test
+ fun addBlockedActivityLocally_onlyAffectsLocalInteractor() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).addGestureBlockedActivity(any())
+ assertThat(lastSeen).contains(component)
+ }
+
+ @Test
+ fun removeBlockedActivityLocally_onlyAffectsLocalInteractor() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).removeGestureBlockedActivity(any())
+ assertThat(lastSeen).isEmpty()
+ }
+
+ @Test
+ fun addBlockedActivity_invokesRepository() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Global)
+ runCurrent()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureRepository).addGestureBlockedActivity(captor.capture())
+ assertThat(captor.firstValue).isEqualTo(component)
+ }
+
+ @Test
+ fun removeBlockedActivity_invokesRepository() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Global)
+ runCurrent()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureRepository).removeGestureBlockedActivity(captor.capture())
+ assertThat(captor.firstValue).isEqualTo(component)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..f74b74a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.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.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+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.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+
+ val underTest = kosmos.alternateBouncerToLockscreenTransitionViewModel
+
+ @Test
+ fun lockscreenAlpha_zeroInitialAlpha() =
+ testScope.runTest {
+ // ViewState starts at 0 alpha.
+ val viewState = ViewStateAccessor(alpha = { 0f })
+ val alpha by collectValues(underTest.lockscreenAlpha(viewState))
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ testScope
+ )
+
+ assertThat(alpha[0]).isEqualTo(0f)
+ // alpha duration is 250ms of the 300ms total, so 0.5f of the total is 0.6
+ assertThat(alpha[1]).isEqualTo(0.6f)
+ assertThat(alpha[2]).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryParentViewAlpha() =
+ testScope.runTest {
+ val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
+
+ // immediately 1f
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0.4f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(.85f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(1f))
+ assertThat(deviceEntryParentViewAlpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() =
+ testScope.runTest {
+ fingerprintPropertyRepository.supportsUdfps()
+ val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha)
+ runCurrent()
+
+ // immediately 1f
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0.1f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(.3f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(.5f))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(bgViewAlpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToLockscreenTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 6f1bc7e..9fea7a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,12 +19,10 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
@@ -38,8 +36,8 @@
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -89,10 +87,7 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(
- FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX,
- )
- .andSceneContainer()
+ return parameterizeSceneContainerFlag()
}
}
@@ -177,25 +172,6 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
- testScope.runTest {
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- overrideResource(R.bool.config_use_split_notification_shade, true)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.large_screen_shade_header_height, 10)
- overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
-
- val paddingTop by collectLastValue(underTest.paddingTopDimen)
-
- configurationRepository.onAnyConfigurationChange()
-
- // Should directly use the header height (flagged off value)
- assertThat(paddingTop).isEqualTo(10)
- }
-
- @Test
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
testScope.runTest {
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
@@ -267,49 +243,8 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@DisableSceneContainer
- fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
- testScope.runTest {
- val headerResourceHeight = 50
- val headerHelperHeight = 100
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(headerHelperHeight)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
- overrideResource(R.dimen.notification_panel_margin_top, 0)
-
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
-
- configurationRepository.onAnyConfigurationChange()
-
- assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight)
- }
-
- @Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- @EnableSceneContainer
- fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
- testScope.runTest {
- val headerResourceHeight = 50
- val headerHelperHeight = 100
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(headerHelperHeight)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
- overrideResource(R.dimen.notification_panel_margin_top, 0)
-
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
-
- configurationRepository.onAnyConfigurationChange()
-
- assertThat(dimens!!.marginTop).isEqualTo(0)
- }
-
- @Test
- @DisableSceneContainer
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
+ fun validateMarginTopWithLargeScreenHeader_usesHelper() =
testScope.runTest {
val headerResourceHeight = 50
val headerHelperHeight = 100
@@ -328,7 +263,6 @@
@Test
@EnableSceneContainer
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
testScope.runTest {
val headerResourceHeight = 50
@@ -626,44 +560,8 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@DisableSceneContainer
- fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
- testScope.runTest {
- val bounds by collectLastValue(underTest.bounds)
-
- // When in split shade
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- overrideResource(R.bool.config_use_split_notification_shade, true)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.large_screen_shade_header_height, 10)
- overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
-
- configurationRepository.onAnyConfigurationChange()
- runCurrent()
-
- // Start on lockscreen
- showLockscreen()
-
- keyguardInteractor.setNotificationContainerBounds(
- NotificationContainerBounds(top = 1f, bottom = 52f)
- )
- runCurrent()
-
- // Top should be equal to bounds (1) - padding adjustment (10)
- assertThat(bounds)
- .isEqualTo(
- NotificationContainerBounds(
- top = -9f,
- bottom = 2f,
- )
- )
- }
-
- @Test
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- @DisableSceneContainer
- fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+ fun boundsOnLockscreenInSplitShade_usesLargeHeaderHelper() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 089db2d..d7c3527 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1225,6 +1225,9 @@
<!-- Label for accessibility action that shows widgets on lock screen on click. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_open_communal_hub">Widgets on lock screen</string>
+ <!-- Label for an accessibility announcement when a widget has been added to the lock screen. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_announcement_communal_widget_added"><xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget added to lock screen</string>
+
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 428cd0e..93ee179 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -724,7 +724,10 @@
@Override
public void onResume(int reason) {
if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
+ mView.clearFocus();
+ mView.clearAccessibilityFocus();
mView.requestFocus();
+ mView.requestAccessibilityFocus();
if (mCurrentSecurityMode != SecurityMode.None) {
int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
if (mView.isSidedSecurityMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4be93cc..d1a5a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
import android.view.View
@@ -197,6 +198,9 @@
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called as the UI determines that a new widget has been added to the grid. */
+ open fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {}
+
/** Called when the grid scroll position has been updated. */
open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) {
currentScrollIndex = firstVisibleItemIndex
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 5b825d8..1a86c71 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -19,16 +19,18 @@
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.UserHandle
import android.util.Log
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -37,6 +39,7 @@
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,8 +77,10 @@
private val uiEventLogger: UiEventLogger,
@CommunalLog logBuffer: LogBuffer,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val communalPrefsInteractor: CommunalPrefsInteractor,
private val metricsLogger: CommunalMetricsLogger,
+ @Application private val context: Context,
+ private val accessibilityManager: AccessibilityManager,
+ private val packageManager: PackageManager,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -156,6 +161,25 @@
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+ override fun onNewWidgetAdded(provider: AppWidgetProviderInfo) {
+ if (!accessibilityManager.isEnabled) {
+ return
+ }
+
+ // Send an accessibility announcement for the newly added widget
+ val widgetLabel = provider.loadLabel(packageManager)
+ val announcementText =
+ context.getString(
+ R.string.accessibility_announcement_communal_widget_added,
+ widgetLabel
+ )
+ accessibilityManager.sendAccessibilityEvent(
+ AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT).apply {
+ contentDescription = announcementText
+ }
+ )
+ }
+
val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
/** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1771f4d..15ddf5b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -80,6 +80,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.navigationbar.gestural.dagger.GestureModule;
import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceConfigPlugin;
@@ -215,6 +216,7 @@
FlagsModule.class,
FlagDependenciesModule.class,
FooterActionsModule.class,
+ GestureModule.class,
InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 9823985..931066d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -27,7 +27,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.graphics.drawable.ColorDrawable;
+import android.service.dreams.DreamActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -63,6 +65,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -135,6 +138,8 @@
private final DreamOverlayComponent mDreamOverlayComponent;
+ private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -222,6 +227,8 @@
private final DreamOverlayStateController mStateController;
+ private final GestureInteractor mGestureInteractor;
+
@VisibleForTesting
public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The dream overlay has entered start.")
@@ -265,6 +272,7 @@
ComponentName homeControlPanelDreamComponent,
DreamOverlayCallbackController dreamOverlayCallbackController,
KeyguardInteractor keyguardInteractor,
+ GestureInteractor gestureInteractor,
@Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
super(executor);
mContext = context;
@@ -281,6 +289,7 @@
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
mSystemDialogsCloser = systemDialogsCloser;
+ mGestureInteractor = gestureInteractor;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -391,6 +400,7 @@
mStarted = true;
updateRedirectWakeup();
+ updateBlockedGestureDreamActivityComponent();
}
private void updateRedirectWakeup() {
@@ -401,6 +411,18 @@
redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
+ private void updateBlockedGestureDreamActivityComponent() {
+ // TODO(b/343815446): We should not be crafting this ActivityInfo ourselves. It should be
+ // in a common place, Such as DreamActivity itself.
+ final ActivityInfo info = new ActivityInfo();
+ info.name = DreamActivity.class.getName();
+ info.packageName = getDreamComponent().getPackageName();
+ mCurrentBlockedGestureDreamActivityComponent = info.getComponentName();
+
+ mGestureInteractor.addGestureBlockedActivity(mCurrentBlockedGestureDreamActivityComponent,
+ GestureInteractor.Scope.Global);
+ }
+
@Override
public void onEndDream() {
resetCurrentDreamOverlayLocked();
@@ -472,6 +494,7 @@
* into the dream window.
*/
private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
+
mWindow = new PhoneWindow(mContext);
// Default to SystemUI name for TalkBack.
mWindow.setTitle(mWindowTitle);
@@ -554,6 +577,14 @@
}
mWindow = null;
+
+ // Always unregister the any set DreamActivity from being blocked from gestures.
+ if (mCurrentBlockedGestureDreamActivityComponent != null) {
+ mGestureInteractor.removeGestureBlockedActivity(
+ mCurrentBlockedGestureDreamActivityComponent, GestureInteractor.Scope.Global);
+ mCurrentBlockedGestureDreamActivityComponent = null;
+ }
+
mStarted = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index d1bbc33..0d01ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import android.util.Log;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -33,6 +34,7 @@
private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
private final Executor mUiBgExecutor;
+ private final static String TAG = "DismissCallbackRegistry";
@Inject
public DismissCallbackRegistry(@UiBackground Executor uiBgExecutor) {
@@ -40,10 +42,12 @@
}
public void addCallback(IKeyguardDismissCallback callback) {
+ Log.d(TAG, "Adding callback: " + callback);
mDismissCallbacks.add(new DismissCallbackWrapper(callback));
}
public void notifyDismissCancelled() {
+ Log.d(TAG, "notifyDismissCancelled(" + mDismissCallbacks.size() + ")");
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
DismissCallbackWrapper callback = mDismissCallbacks.get(i);
mUiBgExecutor.execute(callback::notifyDismissCancelled);
@@ -52,6 +56,7 @@
}
public void notifyDismissSucceeded() {
+ Log.d(TAG, "notifyDismissSucceeded(" + mDismissCallbacks.size() + ")");
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
DismissCallbackWrapper callback = mDismissCallbacks.get(i);
mUiBgExecutor.execute(callback::notifyDismissSucceeded);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a915241..ae830ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -198,7 +198,12 @@
interpolator = Interpolators.LINEAR
duration =
when (toState) {
+ KeyguardState.AOD -> TO_AOD_DURATION
+ KeyguardState.DOZING -> TO_DOZING_DURATION
KeyguardState.GONE -> TO_GONE_DURATION
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.PRIMARY_BOUNCER -> TO_PRIMARY_BOUNCER_DURATION
else -> TRANSITION_DURATION_MS
}.inWholeMilliseconds
}
@@ -211,10 +216,11 @@
companion object {
const val TAG = "FromAlternateBouncerTransitionInteractor"
val TRANSITION_DURATION_MS = 300.milliseconds
- val TO_GONE_DURATION = 500.milliseconds
val TO_AOD_DURATION = TRANSITION_DURATION_MS
- val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS
val TO_DOZING_DURATION = TRANSITION_DURATION_MS
+ val TO_GONE_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = 300.milliseconds
val TO_OCCLUDED_DURATION = TRANSITION_DURATION_MS
+ val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index db33acb..a250b22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -37,6 +37,7 @@
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
@@ -66,6 +67,7 @@
private val alternateBouncerDependencies: Lazy<AlternateBouncerDependencies>,
private val windowManager: Lazy<WindowManager>,
private val layoutInflater: Lazy<LayoutInflater>,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
) : CoreStartable {
private val layoutParams: WindowManager.LayoutParams
get() =
@@ -162,6 +164,7 @@
fun onBackRequested() {
alternateBouncerDependencies.get().viewModel.hideAlternateBouncer()
+ dismissCallbackRegistry.notifyDismissCancelled()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 1b9788f..4d6577c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -74,15 +74,26 @@
val bgView = view.bgView
longPressHandlingView.listener =
object : LongPressHandlingView.Listener {
- override fun onLongPressDetected(view: View, x: Int, y: Int, isA11yAction: Boolean) {
- if (!isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+ override fun onLongPressDetected(
+ view: View,
+ x: Int,
+ y: Int,
+ isA11yAction: Boolean
+ ) {
+ if (
+ !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ ) {
return
}
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch { viewModel.onUserInteraction() }
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
}
}
@@ -95,6 +106,7 @@
launch("$TAG#viewModel.isVisible") {
viewModel.isVisible.collect { isVisible ->
longPressHandlingView.isInvisible = !isVisible
+ view.isClickable = isVisible
}
}
launch("$TAG#viewModel.isLongPressEnabled") {
@@ -131,7 +143,11 @@
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch { viewModel.onUserInteraction() }
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
}
} else {
view.setOnClickListener(null)
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 dc7a649..0032c2f 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
@@ -18,6 +18,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToDozingTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
@@ -80,6 +81,12 @@
@Binds
@IntoSet
+ abstract fun alternateBouncerToLockscreen(
+ impl: AlternateBouncerToLockscreenTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun alternateBouncerToOccluded(
impl: AlternateBouncerToOccludedTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..b04521c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModel.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.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+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.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE_BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views
+ * to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToLockscreenTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAlternateBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ edge = Edge.create(from = ALTERNATE_BOUNCER, to = LOCKSCREEN),
+ )
+
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(startAlpha, 1f, it) },
+ )
+ }
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(1f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 350ceb4..11889c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -85,6 +85,8 @@
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
+ private val alternateBouncerToLockscreenTransitionViewModel:
+ AlternateBouncerToLockscreenTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
@@ -238,6 +240,7 @@
alphaOnShadeExpansion,
keyguardInteractor.dismissAlpha,
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
+ alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 947336d..9eca34f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -76,6 +76,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -95,15 +96,14 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.util.concurrency.BackPanelUiThread;
import com.android.systemui.util.concurrency.UiThreadContext;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.ArrayDeque;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -157,12 +157,7 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- if (edgebackGestureHandlerGetRunningTasksBackground()) {
- mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
- isGestureBlockingActivityRunning()));
- } else {
- mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
- }
+ updateRunningActivityGesturesBlocked();
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -209,8 +204,6 @@
private final Optional<DesktopMode> mDesktopModeOptional;
private final FalsingManager mFalsingManager;
private final Configuration mLastReportedConfig = new Configuration();
- // Activities which should not trigger Back gesture.
- private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
private final Point mDisplaySize = new Point();
private final int mDisplayId;
@@ -227,6 +220,10 @@
mBackGestureTfClassifierProviderProvider;
private final Provider<LightBarController> mLightBarControllerProvider;
+ private final GestureInteractor mGestureInteractor;
+
+ private final JavaAdapter mJavaAdapter;
+
// The left side edge width where touch down is allowed
private int mEdgeWidthLeft;
// The right side edge width where touch down is allowed
@@ -426,7 +423,9 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
Provider<LightBarController> lightBarControllerProvider,
- NotificationShadeWindowController notificationShadeWindowController) {
+ NotificationShadeWindowController notificationShadeWindowController,
+ GestureInteractor gestureInteractor,
+ JavaAdapter javaAdapter) {
mContext = context;
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
@@ -446,7 +445,13 @@
mFalsingManager = falsingManager;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
+ mGestureInteractor = gestureInteractor;
+ mJavaAdapter = javaAdapter;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
+
+ mJavaAdapter.alwaysCollectFlow(mGestureInteractor.getGestureBlockedActivities(),
+ componentNames -> updateRunningActivityGesturesBlocked());
+
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -466,8 +471,9 @@
} else {
String[] gestureBlockingActivities = resources.getStringArray(resId);
for (String gestureBlockingActivity : gestureBlockingActivities) {
- mGestureBlockingActivities.add(
- ComponentName.unflattenFromString(gestureBlockingActivity));
+ mGestureInteractor.addGestureBlockedActivity(
+ ComponentName.unflattenFromString(gestureBlockingActivity),
+ GestureInteractor.Scope.Local);
}
}
} catch (NameNotFoundException e) {
@@ -561,6 +567,15 @@
}
}
+ private void updateRunningActivityGesturesBlocked() {
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
+ }
+
/**
* Called when the nav/task bar is attached.
*/
@@ -1293,7 +1308,8 @@
} else {
mPackageName = "_UNKNOWN";
}
- return topActivity != null && mGestureBlockingActivities.contains(topActivity);
+
+ return topActivity != null && mGestureInteractor.areGesturesBlocked(topActivity);
}
public void setBackAnimation(BackAnimation backAnimation) {
@@ -1342,6 +1358,10 @@
private final Provider<LightBarController> mLightBarControllerProvider;
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final GestureInteractor mGestureInteractor;
+
+ private final JavaAdapter mJavaAdapter;
+
@Inject
public Factory(OverviewProxyService overviewProxyService,
SysUiState sysUiState,
@@ -1361,8 +1381,10 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider,
- NotificationShadeWindowController notificationShadeWindowController) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController,
+ GestureInteractor gestureInteractor,
+ JavaAdapter javaAdapter) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -1382,6 +1404,8 @@
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mGestureInteractor = gestureInteractor;
+ mJavaAdapter = javaAdapter;
}
/** Construct a {@link EdgeBackGestureHandler}. */
@@ -1407,7 +1431,9 @@
mFalsingManager,
mBackGestureTfClassifierProviderProvider,
mLightBarControllerProvider,
- mNotificationShadeWindowController));
+ mNotificationShadeWindowController,
+ mGestureInteractor,
+ mJavaAdapter));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt
new file mode 100644
index 0000000..72a84f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.gestural.dagger
+
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** {@link Module} for gesture related dependencies */
+@Module
+interface GestureModule {
+ /** */
+ @Binds fun gestureRespoitory(impl: GestureRepositoryImpl): GestureRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
new file mode 100644
index 0000000..8f35343
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.gestural.data.respository
+
+import android.content.ComponentName
+import android.util.ArraySet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.withContext
+
+/** A repository for storing gesture related information */
+interface GestureRepository {
+ /** A {@link StateFlow} tracking activities currently blocked from gestures. */
+ val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+
+ /** Adds an activity to be blocked from gestures. */
+ suspend fun addGestureBlockedActivity(activity: ComponentName)
+
+ /** Removes an activity from being blocked from gestures. */
+ suspend fun removeGestureBlockedActivity(activity: ComponentName)
+}
+
+@SysUISingleton
+class GestureRepositoryImpl
+@Inject
+constructor(@Main private val mainDispatcher: CoroutineDispatcher) : GestureRepository {
+ private val _gestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(ArraySet())
+
+ override val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+ get() = _gestureBlockedActivities
+
+ override suspend fun addGestureBlockedActivity(activity: ComponentName) =
+ withContext(mainDispatcher) {
+ _gestureBlockedActivities.emit(
+ _gestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+ )
+ }
+
+ override suspend fun removeGestureBlockedActivity(activity: ComponentName) =
+ withContext(mainDispatcher) {
+ _gestureBlockedActivities.emit(
+ _gestureBlockedActivities.value.toMutableSet().apply { remove(activity) }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
new file mode 100644
index 0000000..6dc5939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.navigationbar.gestural.domain
+
+import android.content.ComponentName
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * {@link GestureInteractor} helps interact with gesture-related logic, including accessing the
+ * underlying {@link GestureRepository}.
+ */
+class GestureInteractor
+@Inject
+constructor(
+ private val gestureRepository: GestureRepository,
+ @Application private val scope: CoroutineScope
+) {
+ enum class Scope {
+ Local,
+ Global
+ }
+
+ private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf())
+ /** A {@link StateFlow} for listening to changes in Activities where gestures are blocked */
+ val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+ get() =
+ combine(
+ gestureRepository.gestureBlockedActivities,
+ _localGestureBlockedActivities.asStateFlow()
+ ) { global, local ->
+ global + local
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), setOf())
+
+ /**
+ * Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link
+ * Activity}.
+ */
+ fun addGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ scope.launch {
+ when (gestureScope) {
+ Scope.Local -> {
+ _localGestureBlockedActivities.emit(
+ _localGestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+ )
+ }
+ Scope.Global -> {
+ gestureRepository.addGestureBlockedActivity(activity)
+ }
+ }
+ }
+ }
+
+ /** Removes an {@link Activity} from being blocked from gestures. */
+ fun removeGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ scope.launch {
+ when (gestureScope) {
+ Scope.Local -> {
+ _localGestureBlockedActivities.emit(
+ _localGestureBlockedActivities.value.toMutableSet().apply {
+ remove(activity)
+ }
+ )
+ }
+ Scope.Global -> {
+ gestureRepository.removeGestureBlockedActivity(activity)
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether the specified {@link Activity} {@link ComponentName} is being blocked from
+ * gestures.
+ */
+ fun areGesturesBlocked(activity: ComponentName): Boolean {
+ return gestureBlockedActivities.value.contains(activity)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
new file mode 100644
index 0000000..02ce74a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object SystemSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSystemSettingsRepository(
+ contentResolver: ContentResolver,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ ): SystemSettingsRepository =
+ SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
index a7970c7..af21e75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -19,14 +19,16 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.SecureSettingsRepositoryModule
+import com.android.systemui.settings.SystemSettingsRepositoryModule
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-@Module(includes = [SecureSettingsRepositoryModule::class])
+@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class])
object NotificationSettingsRepositoryModule {
@Provides
@SysUISingleton
@@ -34,10 +36,12 @@
@Background backgroundScope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
secureSettingsRepository: SecureSettingsRepository,
+ systemSettingsRepository: SystemSettingsRepository,
): NotificationSettingsRepository =
NotificationSettingsRepository(
backgroundScope,
backgroundDispatcher,
- secureSettingsRepository
+ secureSettingsRepository,
+ systemSettingsRepository
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 90a05ef..2956432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -109,7 +109,7 @@
.map {
secureSettings.getIntForUser(
name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
+ default = 0,
userHandle = UserHandle.USER_CURRENT,
) == 1
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9d09595..2a8db56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -46,6 +46,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
@@ -57,7 +58,6 @@
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -98,8 +98,7 @@
globalSettings.registerContentObserverSync(
globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
/* notifyForDescendants = */ true,
- observer
- )
+ observer)
// QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
@@ -171,10 +170,7 @@
class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
VisualInterruptionFilter(
- types = setOf(PEEK),
- reason = "has old `when`",
- uiEventId = HUN_SUPPRESSED_OLD_WHEN
- ) {
+ types = setOf(PEEK), reason = "has old `when`", uiEventId = HUN_SUPPRESSED_OLD_WHEN) {
private fun whenAge(entry: NotificationEntry) =
systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
@@ -201,9 +197,7 @@
class PulseLockscreenVisibilityPrivateSuppressor() :
VisualInterruptionFilter(
- types = setOf(PULSE),
- reason = "hidden by lockscreen visibility override"
- ) {
+ types = setOf(PULSE), reason = "hidden by lockscreen visibility override") {
override fun shouldSuppress(entry: NotificationEntry) =
entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
}
@@ -215,18 +209,13 @@
class HunGroupAlertBehaviorSuppressor() :
VisualInterruptionFilter(
- types = setOf(PEEK, PULSE),
- reason = "suppressive group alert behavior"
- ) {
+ types = setOf(PEEK, PULSE), reason = "suppressive group alert behavior") {
override fun shouldSuppress(entry: NotificationEntry) =
entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
}
class HunSilentNotificationSuppressor() :
- VisualInterruptionFilter(
- types = setOf(PEEK, PULSE),
- reason = "notification isSilent"
- ) {
+ VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "notification isSilent") {
override fun shouldSuppress(entry: NotificationEntry) =
entry.sbn.let { Flags.notificationSilentFlag() && it.notification.isSilent }
}
@@ -273,7 +262,7 @@
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
- private val systemSettings: SystemSettings,
+ private val settingsInteractor: NotificationSettingsInteractor,
private val packageManager: PackageManager,
private val uiEventLogger: UiEventLogger,
private val context: Context,
@@ -298,7 +287,7 @@
// education HUNs.
private var hasShownOnceForDebug = false
- private fun shouldShowEdu() : Boolean {
+ private fun shouldShowEdu(): Boolean {
val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1")
return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug)
}
@@ -361,28 +350,25 @@
return true
}
- /**
- * Show avalanche education HUN from SystemUI.
- */
+ /** Show avalanche education HUN from SystemUI. */
private fun showEdu() {
val res = context.resources
- val titleStr = res.getString(
- com.android.systemui.res.R.string.adaptive_notification_edu_hun_title)
- val textStr = res.getString(
- com.android.systemui.res.R.string.adaptive_notification_edu_hun_text)
- val actionStr = res.getString(
- com.android.systemui.res.R.string.go_to_adaptive_notification_settings)
+ val titleStr =
+ res.getString(com.android.systemui.res.R.string.adaptive_notification_edu_hun_title)
+ val textStr =
+ res.getString(com.android.systemui.res.R.string.adaptive_notification_edu_hun_text)
+ val actionStr =
+ res.getString(com.android.systemui.res.R.string.go_to_adaptive_notification_settings)
val intent = Intent(Settings.ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS)
- val pendingIntent = PendingIntent.getActivity(
- context, 0, intent,
- PendingIntent.FLAG_IMMUTABLE
- )
+ val pendingIntent =
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// Replace "System UI" app name with "Android System"
val bundle = Bundle()
- bundle.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- context.getString(com.android.internal.R.string.android_system_label))
+ bundle.putString(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ context.getString(com.android.internal.R.string.android_system_label))
val builder =
Notification.Builder(context, NotificationChannels.ALERTS)
@@ -400,14 +386,12 @@
notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build())
hasSeenEdu = true
- hasShownOnceForDebug = true;
+ hasShownOnceForDebug = true
}
private fun calculateState(entry: NotificationEntry): State {
- if (
- entry.ranking.isConversation &&
- entry.sbn.notification.getWhen() > avalancheProvider.startTime
- ) {
+ if (entry.ranking.isConversation &&
+ entry.sbn.notification.getWhen() > avalancheProvider.startTime) {
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_NEW_CONVERSATION)
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
@@ -440,10 +424,8 @@
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_COLORIZED)
return State.ALLOW_COLORIZED
}
- if (
- packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
- PERMISSION_GRANTED
- ) {
+ if (packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
+ PERMISSION_GRANTED) {
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY)
return State.ALLOW_EMERGENCY
}
@@ -452,7 +434,6 @@
}
private fun isCooldownEnabled(): Boolean {
- return systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) ==
- 1
+ return settingsInteractor.isCooldownEnabled.value
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 1c476ce..c0d27cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
@@ -72,7 +73,8 @@
private val packageManager: PackageManager,
private val bubbles: Optional<Bubbles>,
private val context: Context,
- private val notificationManager: NotificationManager
+ private val notificationManager: NotificationManager,
+ private val settingsInteractor: NotificationSettingsInteractor
) : VisualInterruptionDecisionProvider {
init {
@@ -183,8 +185,8 @@
if (NotificationAvalancheSuppression.isEnabled) {
addFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger, context, notificationManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor,
+ packageManager, uiEventLogger, context, notificationManager)
)
avalancheProvider.register()
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index b5934ec..9125a91 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -336,7 +336,7 @@
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- fun getString(name: String): String
+ fun getString(name: String): String?
/**
* Store a name/value pair into the database.
@@ -385,15 +385,15 @@
* an integer.
*
* @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- * @return The setting's current value, or 'def' if it is not defined or not a valid integer.
+ * @param default Value to return if the setting is not defined.
+ * @return The setting's current value, or default if it is not defined or not a valid integer.
*/
- fun getInt(name: String, def: Int): Int {
+ fun getInt(name: String, default: Int): Int {
val v = getString(name)
return try {
- v.toInt()
+ v?.toInt() ?: default
} catch (e: NumberFormatException) {
- def
+ default
}
}
@@ -412,7 +412,7 @@
*/
@Throws(SettingNotFoundException::class)
fun getInt(name: String): Int {
- val v = getString(name)
+ val v = getString(name) ?: throw SettingNotFoundException(name)
return try {
v.toInt()
} catch (e: NumberFormatException) {
@@ -441,11 +441,11 @@
* boolean.
*
* @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- * @return The setting's current value, or 'def' if it is not defined or not a valid boolean.
+ * @param default Value to return if the setting is not defined.
+ * @return The setting's current value, or default if it is not defined or not a valid boolean.
*/
- fun getBool(name: String, def: Boolean): Boolean {
- return getInt(name, if (def) 1 else 0) != 0
+ fun getBool(name: String, default: Boolean): Boolean {
+ return getInt(name, if (default) 1 else 0) != 0
}
/**
@@ -579,13 +579,12 @@
companion object {
/** Convert a string to a long, or uses a default if the string is malformed or null */
@JvmStatic
- fun parseLongOrUseDefault(valString: String, def: Long): Long {
- val value: Long
- value =
+ fun parseLongOrUseDefault(valString: String?, default: Long): Long {
+ val value: Long =
try {
- valString.toLong()
+ valString?.toLong() ?: default
} catch (e: NumberFormatException) {
- def
+ default
}
return value
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 848a6e6..ac7c1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -354,12 +354,12 @@
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- override fun getString(name: String): String {
+ override fun getString(name: String): String? {
return getStringForUser(name, userId)
}
/** See [getString]. */
- fun getStringForUser(name: String, userHandle: Int): String
+ fun getStringForUser(name: String, userHandle: Int): String?
/**
* Store a name/value pair into the database. Values written by this method will be overridden
@@ -388,17 +388,17 @@
overrideableByRestore: Boolean
): Boolean
- override fun getInt(name: String, def: Int): Int {
- return getIntForUser(name, def, userId)
+ override fun getInt(name: String, default: Int): Int {
+ return getIntForUser(name, default, userId)
}
/** Similar implementation to [getInt] for the specified [userHandle]. */
- fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
+ fun getIntForUser(name: String, default: Int, userHandle: Int): Int {
val v = getStringForUser(name, userHandle)
return try {
- v.toInt()
+ v?.toInt() ?: default
} catch (e: NumberFormatException) {
- def
+ default
}
}
@@ -408,7 +408,7 @@
/** Similar implementation to [getInt] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
fun getIntForUser(name: String, userHandle: Int): Int {
- val v = getStringForUser(name, userHandle)
+ val v = getStringForUser(name, userHandle) ?: throw SettingNotFoundException(name)
return try {
v.toInt()
} catch (e: NumberFormatException) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 69cc9d5..30326a57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.shared.settings.data.repository.FakeSecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.FakeSystemSettingsRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -38,18 +39,21 @@
private lateinit var testScope: TestScope
private lateinit var secureSettingsRepository: FakeSecureSettingsRepository
+ private lateinit var systemSettingsRepository: FakeSystemSettingsRepository
@Before
fun setUp() {
val testDispatcher = StandardTestDispatcher()
testScope = TestScope(testDispatcher)
secureSettingsRepository = FakeSecureSettingsRepository()
+ systemSettingsRepository = FakeSystemSettingsRepository()
underTest =
NotificationSettingsRepository(
scope = testScope.backgroundScope,
backgroundDispatcher = testDispatcher,
secureSettingsRepository = secureSettingsRepository,
+ systemSettingsRepository = systemSettingsRepository,
)
}
@@ -100,4 +104,22 @@
)
assertThat(historyEnabled).isEqualTo(false)
}
+
+ @Test
+ fun testGetIsCooldownEnabled() =
+ testScope.runTest {
+ val cooldownEnabled by collectLastValue(underTest.isCooldownEnabled)
+
+ systemSettingsRepository.setInt(
+ name = Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+ value = 1,
+ )
+ assertThat(cooldownEnabled).isEqualTo(true)
+
+ systemSettingsRepository.setInt(
+ name = Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+ value = 0,
+ )
+ assertThat(cooldownEnabled).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index a7f36c3..f9509d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -66,7 +66,8 @@
packageManager,
Optional.of(bubbles),
context,
- notificationManager
+ notificationManager,
+ settingsInteractor
)
}
@@ -101,7 +102,7 @@
whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
val avalancheSuppressor = AvalancheSuppressor(
- avalancheProvider, systemClock, systemSettings, packageManager,
+ avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager
)
avalancheSuppressor.hasSeenEdu = false
@@ -125,7 +126,7 @@
whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
val avalancheSuppressor = AvalancheSuppressor(
- avalancheProvider, systemClock, systemSettings, packageManager,
+ avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager
)
avalancheSuppressor.hasSeenEdu = true
@@ -147,7 +148,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -167,7 +168,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -187,7 +188,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -205,7 +206,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -223,7 +224,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -241,7 +242,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -259,7 +260,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
assertFsiNotSuppressed()
@@ -271,7 +272,7 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
@@ -300,7 +301,7 @@
setAllowedEmergencyPkg(true)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
uiEventLogger, context, notificationManager)
) {
ensurePeekState()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index d5ab62b..9d3d9c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -61,6 +61,7 @@
import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
@@ -88,6 +89,7 @@
import com.android.wm.shell.bubbles.Bubbles
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -134,6 +136,7 @@
protected val avalancheProvider: AvalancheProvider = mock()
protected val bubbles: Bubbles = mock()
lateinit var systemSettings: SystemSettings
+ protected val settingsInteractor: NotificationSettingsInteractor = mock()
protected val packageManager: PackageManager = mock()
protected val notificationManager: NotificationManager = mock()
protected abstract val provider: VisualInterruptionDecisionProvider
@@ -164,7 +167,7 @@
userTracker.set(listOf(user), /* currentUserIndex = */ 0)
systemSettings = FakeSettings()
whenever(bubbles.canShowBubbleNotification()).thenReturn(true)
-
+ whenever(settingsInteractor.isCooldownEnabled).thenReturn(MutableStateFlow(true))
provider.start()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index f4cebd7..7fd9c9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -61,7 +62,8 @@
packageManager: PackageManager,
bubbles: Optional<Bubbles>,
context: Context,
- notificationManager: NotificationManager
+ notificationManager: NotificationManager,
+ settingsInteractor: NotificationSettingsInteractor
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -85,7 +87,8 @@
packageManager,
bubbles,
context,
- notificationManager
+ notificationManager,
+ settingsInteractor
)
} else {
NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index d2540a6..bd9cccd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -150,6 +150,7 @@
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
import com.android.systemui.statusbar.KeyboardShortcuts;
@@ -343,7 +344,7 @@
@Mock private NotificationManager mNotificationManager;
@Mock private GlanceableHubContainerController mGlanceableHubContainerController;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
-
+ @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -403,7 +404,8 @@
mPackageManager,
Optional.of(mBubbles),
mContext,
- mNotificationManager);
+ mNotificationManager,
+ mNotificationSettingsInteractor);
mVisualInterruptionDecisionProvider.start();
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index cf87afb..7f33c23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -299,27 +299,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
- int keyguardSplitShadeTopMargin = 100;
- int largeScreenHeaderHeightResource = 70;
- when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
- .thenReturn(keyguardSplitShadeTopMargin);
- when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height))
- .thenReturn(largeScreenHeaderHeightResource);
- mClockPositionAlgorithm.loadDimens(mContext, mResources);
- givenLockScreen();
- mIsSplitShade = true;
- // WHEN the position algorithm is run
- positionClock();
- // THEN the notif padding makes up lacking margin (margin - header height).
- int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightResource;
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
+ public void notifPaddingMakesUpToFullMarginInSplitShade_usesHelper() {
int keyguardSplitShadeTopMargin = 100;
int largeScreenHeaderHeightHelper = 50;
int largeScreenHeaderHeightResource = 70;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index 5ac6110..b0acd03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -275,6 +275,18 @@
}
@Test
+ fun getInt_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+ }
+
+ @Test
+ fun getInt_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) }
+ }
+
+ @Test
fun getBool_keyPresent_returnValidValue() {
mSettings.putBool(TEST_SETTING, true)
assertThat(mSettings.getBool(TEST_SETTING)).isTrue()
@@ -323,6 +335,18 @@
}
@Test
+ fun getLong_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) }
+ }
+
+ @Test
+ fun getLong_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+ }
+
+ @Test
fun getFloat_keyPresent_returnValidValue() {
mSettings.putFloat(TEST_SETTING, 2.5F)
assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F)
@@ -346,6 +370,18 @@
assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
}
+ @Test
+ fun getFloat_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) }
+ }
+
+ @Test
+ fun getFloat_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+ }
+
private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy {
private val mContentResolver = mock(ContentResolver::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index 5f7420d..ead9939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -23,6 +23,7 @@
import android.os.Handler
import android.os.Looper
import android.provider.Settings
+import android.provider.Settings.SettingNotFoundException
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -433,6 +434,18 @@
}
@Test
+ fun getInt_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+ }
+
+ @Test
+ fun getInt_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) }
+ }
+
+ @Test
fun getIntForUser_multipleUsers__validResult() {
mSettings.putIntForUser(TEST_SETTING, 1, MAIN_USER_ID)
mSettings.putIntForUser(TEST_SETTING, 2, SECONDARY_USER_ID)
@@ -501,6 +514,18 @@
}
@Test
+ fun getLong_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) }
+ }
+
+ @Test
+ fun getLong_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+ }
+
+ @Test
fun getLongForUser_multipleUsers__validResult() {
mSettings.putLongForUser(TEST_SETTING, 1L, MAIN_USER_ID)
mSettings.putLongForUser(TEST_SETTING, 2L, SECONDARY_USER_ID)
@@ -535,6 +560,18 @@
}
@Test
+ fun getFloat_keyMalformed_throwException() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) }
+ }
+
+ @Test
+ fun getFloat_keyMalformed_returnDefaultValue() {
+ mSettings.putString(TEST_SETTING, "nan")
+ assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+ }
+
+ @Test
fun getFloatForUser_multipleUsers__validResult() {
mSettings.putFloatForUser(TEST_SETTING, 1F, MAIN_USER_ID)
mSettings.putFloatForUser(TEST_SETTING, 2F, SECONDARY_USER_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d6b4d2b..2457eb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -123,6 +123,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeWindowLogger;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -486,7 +487,8 @@
mock(PackageManager.class),
Optional.of(mock(Bubbles.class)),
mContext,
- mock(NotificationManager.class)
+ mock(NotificationManager.class),
+ mock(NotificationSettingsInteractor.class)
);
interruptionDecisionProvider.start();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
new file mode 100644
index 0000000..9bd346e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.gesture.data
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepositoryImpl
+
+val Kosmos.gestureRepository: GestureRepository by
+ Kosmos.Fixture { GestureRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
new file mode 100644
index 0000000..658aaa6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.gesture.domain
+
+import com.android.systemui.keyguard.gesture.data.gestureRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+
+val Kosmos.gestureInteractor: GestureInteractor by
+ Kosmos.Fixture {
+ GestureInteractor(gestureRepository = gestureRepository, scope = applicationCoroutineScope)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
index 6eb8a49..2919d3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
@@ -49,6 +50,7 @@
alternateBouncerDependencies = { alternateBouncerDependencies },
windowManager = { windowManager },
layoutInflater = { mockedLayoutInflater },
+ dismissCallbackRegistry = dismissCallbackRegistry,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..6c644ee
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToLockscreenTransitionViewModel by Fixture {
+ AlternateBouncerToLockscreenTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 3c5baa5..82860fc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -40,6 +40,8 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ alternateBouncerToLockscreenTransitionViewModel =
+ alternateBouncerToLockscreenTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
index a75d2bc..ecfc168 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.systemSettingsRepository
val Kosmos.notificationSettingsRepository by
Kosmos.Fixture {
@@ -27,5 +28,6 @@
scope = testScope.backgroundScope,
backgroundDispatcher = testDispatcher,
secureSettingsRepository = secureSettingsRepository,
+ systemSettingsRepository = systemSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..01f19ae
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.systemSettingsRepository: SystemSettingsRepository by
+ Kosmos.Fixture { fakeSystemSettingsRepository }
+val Kosmos.fakeSystemSettingsRepository by Kosmos.Fixture { FakeSystemSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index 6417779..65f4122 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -51,6 +51,7 @@
mDispatcher = dispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
throw new UnsupportedOperationException(
@@ -58,6 +59,7 @@
+ "GlobalSettings.registerContentObserver helpful instead.");
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mDispatcher;
@@ -65,7 +67,7 @@
@Override
public void registerContentObserverSync(Uri uri, boolean notifyDescendants,
- ContentObserver settingsObserver) {
+ @NonNull ContentObserver settingsObserver) {
List<ContentObserver> observers;
mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
observers = mContentObserversAllUsers.get(uri.toString());
@@ -73,25 +75,26 @@
}
@Override
- public void unregisterContentObserverSync(ContentObserver settingsObserver) {
+ public void unregisterContentObserverSync(@NonNull ContentObserver settingsObserver) {
for (Map.Entry<String, List<ContentObserver>> entry :
mContentObserversAllUsers.entrySet()) {
entry.getValue().remove(settingsObserver);
}
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Uri.withAppendedPath(CONTENT_URI, name);
}
@Override
- public String getString(String name) {
+ public String getString(@NonNull String name) {
return mValues.get(getUriFor(name).toString());
}
@Override
- public boolean putString(String name, String value) {
+ public boolean putString(@NonNull String name, @NonNull String value) {
return putString(name, value, null, false);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e4e2481..3f0318b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -26,7 +26,9 @@
import android.util.Pair;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.settings.UserTracker;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -42,6 +44,7 @@
new HashMap<>();
private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
private final CoroutineDispatcher mDispatcher;
+ private final UserTracker mUserTracker;
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
@UserIdInt
@@ -54,42 +57,55 @@
@Deprecated
public FakeSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
+ mUserTracker = new FakeUserTracker();
}
public FakeSettings(CoroutineDispatcher dispatcher) {
mDispatcher = dispatcher;
+ mUserTracker = new FakeUserTracker();
}
- public FakeSettings(String initialKey, String initialValue) {
- mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
+ public FakeSettings(CoroutineDispatcher dispatcher, UserTracker userTracker) {
+ mDispatcher = dispatcher;
+ mUserTracker = userTracker;
+ }
+
+ @VisibleForTesting
+ FakeSettings(String initialKey, String initialValue) {
+ this();
putString(initialKey, initialValue);
}
- public FakeSettings(Map<String, String> initialValues) {
- mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
+ @VisibleForTesting
+ FakeSettings(Map<String, String> initialValues) {
+ this();
for (Map.Entry<String, String> kv : initialValues.entrySet()) {
putString(kv.getKey(), kv.getValue());
}
}
@Override
+ @NonNull
public ContentResolver getContentResolver() {
- return null;
+ throw new UnsupportedOperationException(
+ "FakeSettings.getContentResolver is not implemented");
}
+ @NonNull
@Override
public UserTracker getUserTracker() {
- return null;
+ return mUserTracker;
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mDispatcher;
}
@Override
- public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants,
- ContentObserver settingsObserver, int userHandle) {
+ public void registerContentObserverForUserSync(@NonNull Uri uri, boolean notifyDescendants,
+ @NonNull ContentObserver settingsObserver, int userHandle) {
List<ContentObserver> observers;
if (userHandle == UserHandle.USER_ALL) {
mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
@@ -103,19 +119,18 @@
}
@Override
- public void unregisterContentObserverSync(ContentObserver settingsObserver) {
- for (SettingsKey key : mContentObservers.keySet()) {
- List<ContentObserver> observers = mContentObservers.get(key);
+ public void unregisterContentObserverSync(@NonNull ContentObserver settingsObserver) {
+ for (List<ContentObserver> observers : mContentObservers.values()) {
observers.remove(settingsObserver);
}
- for (String key : mContentObserversAllUsers.keySet()) {
- List<ContentObserver> observers = mContentObserversAllUsers.get(key);
+ for (List<ContentObserver> observers : mContentObserversAllUsers.values()) {
observers.remove(settingsObserver);
}
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Uri.withAppendedPath(CONTENT_URI, name);
}
@@ -129,33 +144,34 @@
}
@Override
- public String getString(String name) {
+ public String getString(@NonNull String name) {
return getStringForUser(name, getUserId());
}
@Override
- public String getStringForUser(String name, int userHandle) {
+ public String getStringForUser(@NonNull String name, int userHandle) {
return mValues.get(new SettingsKey(userHandle, getUriFor(name).toString()));
}
@Override
- public boolean putString(String name, String value, boolean overrideableByRestore) {
+ public boolean putString(@NonNull String name, @NonNull String value,
+ boolean overrideableByRestore) {
return putStringForUser(name, value, null, false, getUserId(), overrideableByRestore);
}
@Override
- public boolean putString(String name, String value) {
+ public boolean putString(@NonNull String name, @NonNull String value) {
return putString(name, value, false);
}
@Override
- public boolean putStringForUser(String name, String value, int userHandle) {
+ public boolean putStringForUser(@NonNull String name, @NonNull String value, int userHandle) {
return putStringForUser(name, value, null, false, userHandle, false);
}
@Override
- public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
- int userHandle, boolean overrideableByRestore) {
+ public boolean putStringForUser(@NonNull String name, @NonNull String value, String tag,
+ boolean makeDefault, int userHandle, boolean overrideableByRestore) {
SettingsKey key = new SettingsKey(userHandle, getUriFor(name).toString());
mValues.put(key, value);
@@ -171,7 +187,8 @@
}
@Override
- public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) {
+ public boolean putString(@NonNull String name, @NonNull String value, @NonNull String tag,
+ boolean makeDefault) {
return putString(name, value);
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index cc8ccc3..7c8fd42 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -68,7 +68,10 @@
srcs: [
"runtime-common-ravenwood-src/**/*.java",
],
- visibility: ["//frameworks/base"],
+ visibility: [
+ // Some tests need to access the utilities.
+ ":__subpackages__",
+ ],
}
java_library {
@@ -318,6 +321,9 @@
android_ravenwood_libgroup {
name: "ravenwood-runtime",
+ data: [
+ "framework-res",
+ ],
libs: [
"100-framework-minus-apex.ravenwood",
"200-kxml2-android",
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index f6885e1..fbf27fa 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -12,6 +12,9 @@
{
"name": "RavenwoodBivalentTest_device"
},
+ {
+ "name": "RavenwoodResApkTest"
+ },
// The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
{
"name": "SystemUIGoogleTests",
diff --git a/ravenwood/resapk_test/Android.bp b/ravenwood/resapk_test/Android.bp
new file mode 100644
index 0000000..c145765
--- /dev/null
+++ b/ravenwood/resapk_test/Android.bp
@@ -0,0 +1,30 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodResApkTest",
+
+ resource_apk: "RavenwoodResApkTest-apk",
+
+ libs: [
+ // Normally, tests shouldn't directly access it, but we need to access RavenwoodCommonUtils
+ // in this test.
+ "ravenwood-runtime-common-ravenwood",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
diff --git a/ravenwood/resapk_test/apk/Android.bp b/ravenwood/resapk_test/apk/Android.bp
new file mode 100644
index 0000000..10ed5e2
--- /dev/null
+++ b/ravenwood/resapk_test/apk/Android.bp
@@ -0,0 +1,14 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "RavenwoodResApkTest-apk",
+
+ sdk_version: "current",
+}
diff --git a/ravenwood/resapk_test/apk/AndroidManifest.xml b/ravenwood/resapk_test/apk/AndroidManifest.xml
new file mode 100644
index 0000000..f34d8b2
--- /dev/null
+++ b/ravenwood/resapk_test/apk/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.ravenwood.restest_apk">
+</manifest>
diff --git a/ravenwood/resapk_test/apk/res/values/strings.xml b/ravenwood/resapk_test/apk/res/values/strings.xml
new file mode 100644
index 0000000..23d4c0f
--- /dev/null
+++ b/ravenwood/resapk_test/apk/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Test string 1 -->
+ <string name="test_string_1" translatable="false" >Test String 1</string>
+</resources>
diff --git a/ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java b/ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java
new file mode 100644
index 0000000..1029ed2
--- /dev/null
+++ b/ravenwood/resapk_test/test/com/android/ravenwood/resapk_test/RavenwoodResApkTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ravenwood.resapk_test;
+
+
+import static junit.framework.TestCase.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodResApkTest {
+ /**
+ * Ensure the file "ravenwood-res.apk" exists.
+ * TODO Check the content of it, once Ravenwood supports resources. The file should
+ * be a copy of RavenwoodResApkTest-apk.apk
+ */
+ @Test
+ public void testResApkExists() {
+ var file = "ravenwood-res-apks/ravenwood-res.apk";
+
+ assertTrue(new File(file).exists());
+ }
+
+ @Test
+ public void testFrameworkResExists() {
+ var file = "ravenwood-data/framework-res.apk";
+
+ assertTrue(new File(
+ RavenwoodCommonUtils.getRavenwoodRuntimePath() + "/" + file).exists());
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index eae516e..9f7fb57 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -1985,12 +1985,13 @@
}
@Override
- public void setAutofillFailure(int sessionId, @NonNull List<AutofillId> ids, int userId) {
+ public void setAutofillFailure(
+ int sessionId, @NonNull List<AutofillId> ids, boolean isRefill, int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service =
peekServiceForUserWithLocalBinderIdentityLocked(userId);
if (service != null) {
- service.setAutofillFailureLocked(sessionId, getCallingUid(), ids);
+ service.setAutofillFailureLocked(sessionId, getCallingUid(), ids, isRefill);
} else if (sVerbose) {
Slog.v(TAG, "setAutofillFailure(): no service for " + userId);
}
@@ -2011,6 +2012,46 @@
}
@Override
+ public void notifyNotExpiringResponseDuringAuth(int sessionId, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyNotExpiringResponseDuringAuth(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.notifyViewEnteredIgnoredDuringAuthCount(sessionId, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void setAutofillIdsAttemptedForRefill(
+ int sessionId, @NonNull List<AutofillId> ids, int userId) {
+ synchronized (mLock) {
+ final AutofillManagerServiceImpl service =
+ peekServiceForUserWithLocalBinderIdentityLocked(userId);
+ if (service != null) {
+ service.setAutofillIdsAttemptedForRefill(sessionId, ids, getCallingUid());
+ } else if (sVerbose) {
+ Slog.v(TAG, "setAutofillIdsAttemptedForRefill(): no service for " + userId);
+ }
+ }
+ }
+
+ @Override
public void finishSession(int sessionId, int userId,
@AutofillCommitReason int commitReason) {
synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 2bf319e..c9f8929 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -464,7 +464,8 @@
}
@GuardedBy("mLock")
- void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) {
+ void setAutofillFailureLocked(
+ int sessionId, int uid, @NonNull List<AutofillId> ids, boolean isRefill) {
if (!isEnabledLocked()) {
Slog.wtf(TAG, "Service not enabled");
return;
@@ -474,7 +475,7 @@
Slog.v(TAG, "setAutofillFailure(): no session for " + sessionId + "(" + uid + ")");
return;
}
- session.setAutofillFailureLocked(ids);
+ session.setAutofillFailureLocked(ids, isRefill);
}
@GuardedBy("mLock")
@@ -492,6 +493,52 @@
}
@GuardedBy("mLock")
+ void notifyNotExpiringResponseDuringAuth(int sessionId, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyNotExpiringResponseDuringAuth(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setNotifyNotExpiringResponseDuringAuth();
+ }
+
+ @GuardedBy("mLock")
+ void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "notifyViewEnteredIgnoredDuringAuthCount(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setLogViewEnteredIgnoredDuringAuth();
+ }
+
+ @GuardedBy("mLock")
+ public void setAutofillIdsAttemptedForRefill(
+ int sessionId, @NonNull List<AutofillId> ids, int uid) {
+ if (!isEnabledLocked()) {
+ Slog.wtf(TAG, "Service not enabled");
+ return;
+ }
+ final Session session = mSessions.get(sessionId);
+ if (session == null || uid != session.uid) {
+ Slog.v(TAG, "setAutofillIdsAttemptedForRefill(): no session for "
+ + sessionId + "(" + uid + ")");
+ return;
+ }
+ session.setAutofillIdsAttemptedForRefillLocked(ids);
+ }
+
+ @GuardedBy("mLock")
void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) {
if (!isEnabledLocked()) {
Slog.wtf(TAG, "Service not enabled");
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 49ca297..930af5e 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -58,6 +58,7 @@
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -685,6 +686,19 @@
}
/**
+ * Set views_fillable_total_count as long as mEventInternal presents.
+ */
+ public void maybeUpdateViewFillablesForRefillAttempt(List<AutofillId> autofillIds) {
+ mEventInternal.ifPresent(event -> {
+ // These autofill ids would be the ones being re-attempted.
+ event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds);
+ // These autofill id's are being refilled, so they had failed previously.
+ // Note that these autofillIds correspond to the new autofill ids after relayout.
+ event.mFailedAutofillIds = new ArraySet<>(autofillIds);
+ });
+ }
+
+ /**
* Set how many views are filtered from fill because they are not in current session
*/
public void maybeSetFilteredFillableViewsCount(int filteredViewsCount) {
@@ -697,9 +711,16 @@
* Set views_filled_failure_count using failure count as long as mEventInternal
* presents.
*/
- public void maybeSetViewFillFailureCounts(int failureCount) {
+ public void maybeSetViewFillFailureCounts(@NonNull List<AutofillId> ids, boolean isRefill) {
mEventInternal.ifPresent(event -> {
- event.mViewFillFailureCount = failureCount;
+ int failureCount = ids.size();
+ if (isRefill) {
+ event.mViewFailedOnRefillCount = failureCount;
+ } else {
+ event.mViewFillFailureCount = failureCount;
+ event.mViewFailedPriorToRefillCount = failureCount;
+ event.mFailedAutofillIds = new ArraySet<>(ids);
+ }
});
}
@@ -719,7 +740,7 @@
* Set views_filled_failure_count using failure count as long as mEventInternal
* presents.
*/
- public void maybeAddSuccessId(AutofillId autofillId) {
+ public synchronized void maybeAddSuccessId(AutofillId autofillId) {
mEventInternal.ifPresent(event -> {
ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill;
if (autofillIds == null) {
@@ -727,9 +748,21 @@
+ " successfully filled");
event.mViewFilledButUnexpectedCount++;
} else if (autofillIds.contains(autofillId)) {
- if (sVerbose) {
- Slog.v(TAG, "Logging autofill for id:" + autofillId);
+ ArraySet<AutofillId> failedIds = event.mFailedAutofillIds;
+ if (failedIds.contains(autofillId)) {
+ if (sVerbose) {
+ Slog.v(TAG, "Logging autofill refill of id:" + autofillId);
+ }
+ // This indicates the success after refill attempt
+ event.mViewFilledSuccessfullyOnRefillCount++;
+ // Remove so if we don't reprocess duplicate requests
+ failedIds.remove(autofillId);
+ } else {
+ if (sVerbose) {
+ Slog.v(TAG, "Logging autofill for id:" + autofillId);
+ }
}
+ // Common actions to take irrespective of being filled by refill attempt or not.
event.mViewFillSuccessCount++;
autofillIds.remove(autofillId);
event.mAlreadyFilledAutofillIds.add(autofillId);
@@ -746,6 +779,23 @@
});
}
+ /**
+ * Set how many views are filtered from fill because they are not in current session
+ */
+ public void maybeSetNotifyNotExpiringResponseDuringAuth() {
+ mEventInternal.ifPresent(event -> {
+ event.mFixExpireResponseDuringAuthCount++;
+ });
+ }
+ /**
+ * Set how many views are filtered from fill because they are not in current session
+ */
+ public void notifyViewEnteredIgnoredDuringAuthCount() {
+ mEventInternal.ifPresent(event -> {
+ event.mNotifyViewEnteredIgnoredDuringAuthCount++;
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -933,6 +983,7 @@
int mNotifyViewEnteredIgnoredDuringAuthCount = 0;
ArraySet<AutofillId> mAutofillIdsAttemptedAutofill;
+ ArraySet<AutofillId> mFailedAutofillIds = new ArraySet<>();
ArraySet<AutofillId> mAlreadyFilledAutofillIds = new ArraySet<>();
// Not logged - used for internal logic
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 21df7a5..b7508b4 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -5360,6 +5360,8 @@
saveTriggerId = null;
}
+ boolean hasAuthentication = (response.getAuthentication() != null);
+
// Must also track that are part of datasets, otherwise the FillUI won't be hidden when
// they go away (if they're not savable).
@@ -5379,6 +5381,9 @@
}
}
}
+ if (dataset.getAuthentication() != null) {
+ hasAuthentication = true;
+ }
}
}
@@ -5390,7 +5395,7 @@
+ " hasSaveInfo: " + (saveInfo != null));
}
mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible,
- saveOnFinish, toArray(fillableIds), saveTriggerId);
+ saveOnFinish, toArray(fillableIds), saveTriggerId, hasAuthentication);
} catch (RemoteException e) {
Slog.w(TAG, "Cannot set tracked ids", e);
}
@@ -5400,7 +5405,7 @@
* Sets the state of views that failed to autofill.
*/
@GuardedBy("mLock")
- void setAutofillFailureLocked(@NonNull List<AutofillId> ids) {
+ void setAutofillFailureLocked(@NonNull List<AutofillId> ids, boolean isRefill) {
if (sVerbose && !ids.isEmpty()) {
Slog.v(TAG, "Total views that failed to populate: " + ids.size());
}
@@ -5418,7 +5423,7 @@
Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString());
}
}
- mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size());
+ mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids, isRefill);
}
/**
@@ -5435,6 +5440,23 @@
mPresentationStatsEventLogger.maybeAddSuccessId(id);
}
+ /**
+ * Sets the state of views that failed to autofill.
+ */
+ void setNotifyNotExpiringResponseDuringAuth() {
+ synchronized (mLock) {
+ mPresentationStatsEventLogger.maybeSetNotifyNotExpiringResponseDuringAuth();
+ }
+ }
+ /**
+ * Sets the state of views that failed to autofill.
+ */
+ void setLogViewEnteredIgnoredDuringAuth() {
+ synchronized (mLock) {
+ mPresentationStatsEventLogger.notifyViewEnteredIgnoredDuringAuthCount();
+ }
+ }
+
@GuardedBy("mLock")
private void replaceResponseLocked(@NonNull FillResponse oldResponse,
@NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
@@ -6665,6 +6687,11 @@
}
}
+ @GuardedBy("mLock")
+ public void setAutofillIdsAttemptedForRefillLocked(@NonNull List<AutofillId> ids) {
+ mPresentationStatsEventLogger.maybeUpdateViewFillablesForRefillAttempt(ids);
+ }
+
private AutoFillUI getUiForShowing() {
synchronized (mLock) {
mUi.setCallback(this);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0fa5260..2e1416b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6653,9 +6653,10 @@
// If unbound while waiting to start and there is no connection left in this service,
// remove the pending service
- if (s.getConnections().isEmpty()) {
+ if (s.getConnections().isEmpty() && !s.startRequested) {
mPendingServices.remove(s);
mPendingBringups.remove(s);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Removed pending service: " + s);
}
if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index 539a00d..a33d70a 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -19,6 +19,7 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
@@ -39,6 +40,10 @@
// Number of retries <Request Active Source> is sent if no device answers this message.
private static final int MAX_SEND_RETRY_COUNT = 1;
+ // Timeout to wait for the LauncherX API call to be completed.
+ @VisibleForTesting
+ protected static final int TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS = 10000;
+
private int mSendRetryCount = 0;
@@ -55,7 +60,7 @@
// We wait for default timeout to allow the message triggered by the LauncherX API call to
// be sent by the TV and another default timeout in case the message has to be answered
// (e.g. TV sent a <Set Stream Path> or <Routing Change>).
- addTimer(mState, HdmiConfig.TIMEOUT_MS * 2);
+ addTimer(mState, TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
return true;
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 55de9aa..a06ad14 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1254,9 +1254,10 @@
/**
* Start drag and drop.
*
- * @param fromChannel The input channel that is currently receiving a touch gesture that should
- * be turned into the drag pointer.
- * @param dragAndDropChannel The input channel associated with the system drag window.
+ * @param fromChannelToken The token of the input channel that is currently receiving a touch
+ * gesture that should be turned into the drag pointer.
+ * @param dragAndDropChannelToken The token of the input channel associated with the system drag
+ * window.
* @return true if drag and drop was successfully started, false otherwise.
*/
public boolean startDragAndDrop(@NonNull IBinder fromChannelToken,
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index cdea6ff..0b3f3f0 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -227,7 +227,6 @@
? overlayWindowToken : null;
synchronized (ImfLock.class) {
mCurVisibleImeLayeringOverlay = overlay;
-
}
}
@@ -577,7 +576,6 @@
}
@GuardedBy("ImfLock.class")
- @VisibleForTesting
ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) {
final ImeTargetWindowState state = getWindowStateOrNull(windowToken);
if (state != null && state.isRequestedImeVisible() && mInputShown && !interactive) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 016abff..4179edd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1315,10 +1315,24 @@
nv.rank, nv.count);
StatusBarNotification sbn = r.getSbn();
- cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
- sbn.getId(), FLAG_AUTO_CANCEL,
- FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
- false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
+ // Notifications should be cancelled on click if they have been lifetime extended,
+ // regardless of presence or absence of FLAG_AUTO_CANCEL.
+ if (lifetimeExtensionRefactor()
+ && (sbn.getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) != 0) {
+ cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
+ sbn.getId(), FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+ | FLAG_BUBBLE,
+ false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
+
+ } else {
+ // Otherwise, only FLAG_AUTO_CANCEL notifications should be canceled on click.
+ cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
+ sbn.getId(), FLAG_AUTO_CANCEL,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
+ false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
+ }
nv.recycle();
reportUserInteraction(r);
mAssistants.notifyAssistantNotificationClicked(r);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a17c48d..4425079 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3825,13 +3825,13 @@
// This also has the (beneficial) side effect where if a package disappears from an
// APEX, leaving only a /data copy, it will lose its apexModuleName.
//
- // This must be done before scanSystemPackageLI as that will throw in the case of a
+ // This must be done before scanPackageForInitLI as that will throw in the case of a
// system -> data package.
disabledPkgSetting.setApexModuleName(activeApexInfo.apexModuleName);
}
}
- final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
+ final Pair<ScanResult, Boolean> scanResultPair = scanPackageForInitLI(
parsedPackage, parseFlags, scanFlags, user);
final ScanResult scanResult = scanResultPair.first;
boolean shouldHideSystemApp = scanResultPair.second;
@@ -4066,7 +4066,7 @@
}
}
- private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
+ private Pair<ScanResult, Boolean> scanPackageForInitLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags,
@Nullable UserHandle user) throws PackageManagerException {
@@ -4179,7 +4179,7 @@
ParsingPackageUtils.getSigningDetails(input, parsedPackage,
false /*skipVerify*/);
if (result.isError()) {
- throw new PrepareFailure("Failed collect during scanSystemPackageLI",
+ throw new PrepareFailure("Failed collect during scanPackageForInitLI",
result.getException());
}
disabledPkgSetting.setSigningDetails(result.getResult());
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 6abef8b..c849a37 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.View.DRAG_FLAG_GLOBAL;
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
@@ -228,7 +227,7 @@
final Display display = displayContent.getDisplay();
touchFocusTransferredFuture = mCallback.get().registerInputChannel(
mDragState, display, mService.mInputManager,
- callingWin.mInputChannel);
+ callingWin.mInputChannelToken);
} else {
// Skip surface logic for a drag triggered by an AccessibilityAction
mDragState.broadcastDragStartedLocked(touchX, touchY);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c6aaf4e..48a5050 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -41,7 +41,6 @@
import android.view.IInputFilter;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IWindow;
-import android.view.InputChannel;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -377,10 +376,10 @@
public interface IDragDropCallback {
default CompletableFuture<Boolean> registerInputChannel(
DragState state, Display display, InputManagerService service,
- InputChannel source) {
+ IBinder sourceInputChannelToken) {
return state.register(display)
.thenApply(unused ->
- service.startDragAndDrop(source.getToken(), state.getInputToken()));
+ service.startDragAndDrop(sourceInputChannelToken, state.getInputToken()));
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a218068..4db62478 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9778,7 +9778,7 @@
Slog.e(TAG, "Host window not found");
return;
}
- if (hostWindow.mInputChannel == null) {
+ if (hostWindow.mInputChannelToken == null) {
Slog.e(TAG, "Host window does not have an input channel");
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1cc5a8b..153d41b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -638,7 +638,7 @@
/**
* Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
*/
- InputChannel mInputChannel;
+ private InputChannel mInputChannel;
/**
* The token will be assigned to {@link InputWindowHandle#token} if this window can receive
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 8981b7a..dd3b33e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -311,10 +311,8 @@
final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
ImeVisibilityResult.class);
- synchronized (ImfLock.class) {
- verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
- targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture());
- }
+ verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
+ targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture());
final IBinder imeInputTarget = targetCaptor.getValue();
final ImeVisibilityResult result = resultCaptor.getValue();
diff --git a/services/tests/performancehinttests/Android.bp b/services/tests/performancehinttests/Android.bp
index 79a1f58..1692921c 100644
--- a/services/tests/performancehinttests/Android.bp
+++ b/services/tests/performancehinttests/Android.bp
@@ -32,18 +32,3 @@
enabled: false,
},
}
-
-android_ravenwood_test {
- name: "PerformanceHintTestsRavenwood",
- static_libs: [
- "androidx.annotation_annotation",
- "androidx.test.rules",
- "flag-junit",
- "services.core",
-
- ],
- srcs: [
- "src/**/*.java",
- ],
- auto_gen_config: true,
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 87b52e6..f98bbf9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -28,6 +28,7 @@
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -1792,7 +1793,7 @@
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1825,7 +1826,7 @@
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1861,7 +1862,7 @@
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1904,7 +1905,7 @@
mTestLooper.dispatchAll();
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1941,7 +1942,7 @@
mHdmiControlService.sendCecCommand(setStreamPathFromTv);
// Skip the LauncherX API timeout.
- mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_LAUNCHERX_API_CALL_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c1f5a01..1b999e4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3105,6 +3105,29 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
+ public void testLifetimeExtendedCancelledOnClick() throws Exception {
+ // Adds a lifetime extended notification.
+ final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1,
+ null, false);
+ notif.getSbn().getNotification().flags =
+ Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ mService.addNotification(notif);
+ // Verify that the notification is posted and active.
+ assertThat(mBinderService.getActiveNotifications(mPkg).length).isEqualTo(1);
+
+ // Click the notification.
+ final NotificationVisibility nv = NotificationVisibility.obtain(notif.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+ notif.getKey(), nv);
+ waitForIdle();
+
+ // The notification has been cancelled.
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(0);
+ }
+
+ @Test
public void testCancelNotificationWithTag_fromApp_cannotCancelFgsChild()
throws Exception {
when(mAmi.applyForegroundServiceNotification(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 1072ef0..4a9d5c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -156,7 +156,6 @@
window.openInputChannel(channel);
window.mHasSurface = true;
mWm.mWindowMap.put(window.mClient.asBinder(), window);
- mWm.mInputToWindowMap.put(window.mInputChannelToken, window);
return window;
}