Merge "Fix ZenModeRepositoryTest flag flipping" into main
diff --git a/BAL_OWNERS b/BAL_OWNERS
index d56a1d4..ec779e7 100644
--- a/BAL_OWNERS
+++ b/BAL_OWNERS
@@ -2,4 +2,6 @@
achim@google.com
topjohnwu@google.com
lus@google.com
+haok@google.com
+wnan@google.com
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 dfa7206..ee03e4b 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -120,6 +120,7 @@
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.WorkSource;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -1794,7 +1795,8 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
- mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
+ mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
+ && UserManager.supportsMultipleUsers();
if (mStartUserBeforeScheduledAlarms) {
mUserWakeupStore = new UserWakeupStore();
mUserWakeupStore.init();
@@ -3015,7 +3017,7 @@
mUseFrozenStateToDropListenerAlarms);
pw.println();
pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
- mStartUserBeforeScheduledAlarms);
+ Flags.startUserBeforeScheduledAlarms());
pw.decreaseIndent();
pw.println();
pw.println();
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index bf67187..0a3ae4f 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -16,9 +16,9 @@
package android.platform.coverage
+import com.android.tools.metalava.model.CallableItem
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import java.io.File
import java.io.FileWriter
@@ -40,24 +40,24 @@
fun extractFlaggedApisFromClass(
classItem: ClassItem,
- methods: List<MethodItem>,
+ callables: List<CallableItem>,
packageName: String,
builder: FlagApiMap.Builder
) {
- if (methods.isEmpty()) return
+ if (callables.isEmpty()) return
val classFlag = getClassFlag(classItem)
- for (method in methods) {
- val methodFlag = getFlagAnnotation(method) ?: classFlag
+ for (callable in callables) {
+ val callableFlag = getFlagAnnotation(callable) ?: classFlag
val api =
JavaMethod.newBuilder()
.setPackageName(packageName)
.setClassName(classItem.fullName())
- .setMethodName(method.name())
- for (param in method.parameters()) {
+ .setMethodName(callable.name())
+ for (param in callable.parameters()) {
api.addParameters(param.type().toTypeString())
}
- if (methodFlag != null) {
- addFlaggedApi(builder, api, methodFlag)
+ if (callableFlag != null) {
+ addFlaggedApi(builder, api, callableFlag)
}
}
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 978a8f9..e3dbb2b 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -747,6 +747,10 @@
* {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}.
* </p>
*
+ * <p>This function returns an empty array if
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+ * is not supported.</p>
+ *
* @return an array of supported high speed video recording sizes
* @see #getHighSpeedVideoFpsRangesFor(Size)
* @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
@@ -836,6 +840,10 @@
* supported for the same recording rate.</li>
* </p>
*
+ * <p>This function returns an empty array if
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}
+ * is not supported.</p>
+ *
* @return an array of supported high speed video recording FPS ranges The upper bound of
* returned ranges is guaranteed to be larger or equal to 120.
* @see #getHighSpeedVideoSizesFor
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d560399..855c309 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,7 +16,6 @@
package android.inputmethodservice;
-import static android.view.inputmethod.Flags.predictiveBackIme;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
@@ -57,6 +56,7 @@
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
import static android.view.inputmethod.Flags.ctrlShiftShortcut;
+import static android.view.inputmethod.Flags.predictiveBackIme;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -101,7 +101,6 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.InputType;
import android.text.Layout;
@@ -4348,7 +4347,7 @@
* @hide
*/
final void onImeSwitchButtonClickFromClient() {
- mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId(), UserHandle.myUserId());
+ mPrivOps.onImeSwitchButtonClickFromClient(getDisplayId());
}
/**
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 8b1577c..97993b6 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -41,5 +41,5 @@
// There is no order guarantee with respect to the two-way APIs above like
// vibrate/isVibrating/cancel.
oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
- boolean always, String reason, boolean fromIme);
+ String reason, int flags, int privFlags);
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 2a62c24..5339d73 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,13 +206,12 @@
}
@Override
- public void performHapticFeedback(
- int constant, boolean always, String reason, boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
if (mVibratorManager == null) {
Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
return;
}
- mVibratorManager.performHapticFeedback(constant, always, reason, fromIme);
+ mVibratorManager.performHapticFeedback(constant, reason, flags, privFlags);
}
@Override
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index c80bcac..a9846ba 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -147,15 +147,14 @@
}
@Override
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason, int flags, int privFlags) {
if (mService == null) {
Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
return;
}
try {
- mService.performHapticFeedback(
- mUid, mContext.getDeviceId(), mPackageName, constant, always, reason, fromIme);
+ mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant,
+ reason, flags, privFlags);
} catch (RemoteException e) {
Log.w(TAG, "Failed to perform haptic feedback.", e);
}
@@ -245,9 +244,8 @@
}
@Override
- public void performHapticFeedback(int effectId, boolean always, String reason,
- boolean fromIme) {
- SystemVibratorManager.this.performHapticFeedback(effectId, always, reason, fromIme);
+ public void performHapticFeedback(int effectId, String reason, int flags, int privFlags) {
+ SystemVibratorManager.this.performHapticFeedback(effectId, reason, flags, privFlags);
}
@Override
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 8af371c..71c83f2 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -33,6 +33,7 @@
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibratorFrequencyProfile;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -519,17 +520,15 @@
*
* @param constant the ID for the haptic feedback. This should be one of the constants defined
* in {@link HapticFeedbackConstants}.
- * @param always {@code true} if the haptic feedback should be played regardless of the user
- * vibration intensity settings applicable to the corresponding vibration.
- * {@code false} if the vibration for the haptic feedback should respect the applicable
- * vibration intensity settings.
* @param reason the reason for this haptic feedback.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
*
* @hide
*/
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
Log.w(TAG, "performHapticFeedback is not supported");
}
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 513c4bd..2c7a852 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -23,6 +23,7 @@
import android.app.ActivityThread;
import android.content.Context;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
/**
* Provides access to all vibrators from the device, as well as the ability to run them
@@ -142,15 +143,14 @@
*
* @param constant the ID of the requested haptic feedback. Should be one of the constants
* defined in {@link HapticFeedbackConstants}.
- * @param always {@code true} if the haptic feedback should be played regardless of the user
- * vibration intensity settings applicable to the corresponding vibration.
- * {@code false} otherwise.
* @param reason the reason for this haptic feedback.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
* @hide
*/
- public void performHapticFeedback(int constant, boolean always, String reason,
- boolean fromIme) {
+ public void performHapticFeedback(int constant, String reason,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
Log.w(TAG, "performHapticFeedback is not supported");
}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 69228ca..1fe06d4 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -16,11 +16,30 @@
package android.view;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Constants to be used to perform haptic feedback effects via
* {@link View#performHapticFeedback(int)}
*/
public class HapticFeedbackConstants {
+ /** @hide **/
+ @IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_IGNORE_VIEW_SETTING,
+ FLAG_IGNORE_GLOBAL_SETTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /** @hide **/
+ @IntDef(flag = true, prefix = "PRIVATE_FLAG_", value = {
+ PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrivateFlags {}
private HapticFeedbackConstants() {}
@@ -258,4 +277,14 @@
*/
@Deprecated
public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+
+ /**
+ * Flag for {@link android.os.Vibrator#performHapticFeedback(int, boolean, String, int, int)} or
+ * {@link ViewRootImpl#performHapticFeedback(int, boolean, int, int)}: Perform the haptic
+ * feedback with the input method vibration settings, e.g. applying the keyboard vibration
+ * user settings to the KEYBOARD_* constants.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS = 0x0001;
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 070d33b..14407ca 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,13 +140,13 @@
int seqId);
@UnsupportedAppUsage
- boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+ boolean performHapticFeedback(int effectId, int flags, int privFlags);
/**
* Called by attached views to perform predefined haptic feedback without requiring VIBRATE
* permission.
*/
- oneway void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme);
+ oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
/**
* Initiate the drag operation itself
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 88d7a83..2edbcc2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28636,20 +28636,20 @@
return false;
}
- final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
- boolean fromIme = false;
- if (mAttachInfo.mViewRootImpl != null) {
- fromIme = mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+ int privFlags = 0;
+ if (mAttachInfo.mViewRootImpl != null
+ && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
+ privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
}
if (Flags.useVibratorHapticFeedback()) {
if (!mAttachInfo.canPerformHapticFeedback()) {
return false;
}
- getSystemVibrator().performHapticFeedback(
- feedbackConstant, always, "View#performHapticFeedback", fromIme);
+ getSystemVibrator().performHapticFeedback(feedbackConstant,
+ "View#performHapticFeedback", flags, privFlags);
return true;
}
- return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always, fromIme);
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
}
private Vibrator getSystemVibrator() {
@@ -31684,7 +31684,10 @@
interface Callbacks {
void playSoundEffect(int effectId);
- boolean performHapticFeedback(int effectId, boolean always, boolean fromIme);
+
+ boolean performHapticFeedback(int effectId,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e5b17c8..596726f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9666,18 +9666,18 @@
* {@inheritDoc}
*/
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
return false;
}
try {
if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
- mWindowSession.performHapticFeedbackAsync(effectId, always, fromIme);
+ mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
return true;
} else {
// Original blocking binder call path.
- return mWindowSession.performHapticFeedback(effectId, always, fromIme);
+ return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
}
} catch (RemoteException e) {
return false;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 55f22a6..7871858 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -503,13 +503,13 @@
}
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
return false;
}
@Override
- public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
- performHapticFeedback(effectId, always, fromIme);
+ public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+ performHapticFeedback(effectId, flags, privFlags);
}
@Override
diff --git a/core/java/android/view/accessibility/a11ychecker/Android.bp b/core/java/android/view/accessibility/a11ychecker/Android.bp
deleted file mode 100644
index e5a577c..0000000
--- a/core/java/android/view/accessibility/a11ychecker/Android.bp
+++ /dev/null
@@ -1,7 +0,0 @@
-java_library_static {
- name: "A11yChecker",
- srcs: [
- "*.java",
- ],
- visibility: ["//visibility:public"],
-}
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 739cf0e..0d5e37e 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -75,8 +75,8 @@
/**
* The core implementation to obtain {@link WindowMetrics}
*
- * @param isMaximum {@code true} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
- * {@code false} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
+ * @param isMaximum {@code false} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
+ * {@code true} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
*/
private WindowMetrics getWindowMetricsInternal(boolean isMaximum) {
final Rect bounds;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 82ae76a..ac4c066 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -43,7 +43,7 @@
void switchToPreviousInputMethod(in AndroidFuture future /* T=Boolean */);
void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
- void onImeSwitchButtonClickFromClient(int displayId, int userId);
+ void onImeSwitchButtonClickFromClient(int displayId);
void notifyUserActionAsync();
void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
in ImeTracker.Token statsToken);
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index f50f02e..2daf0fd 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -378,16 +378,16 @@
}
/**
- * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int, int)}
+ * Calls {@link IInputMethodPrivilegedOperations#onImeSwitchButtonClickFromClient(int)}
*/
@AnyThread
- public void onImeSwitchButtonClickFromClient(int displayId, int userId) {
+ public void onImeSwitchButtonClickFromClient(int displayId) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
}
try {
- ops.onImeSwitchButtonClickFromClient(displayId, userId);
+ ops.onImeSwitchButtonClickFromClient(displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7c62615..c07fd38 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,7 +27,6 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
-#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -42,10 +41,8 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
-#include <thread>
#include <optional>
#include <sstream>
-#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -264,13 +261,6 @@
jfieldID mMixerBehavior;
} gAudioMixerAttributesField;
-static struct {
- jclass clazz;
- jmethodID run;
-} gRunnableClassInfo;
-
-static JavaVM* gVm;
-
static Mutex gLock;
enum AudioError {
@@ -3372,55 +3362,6 @@
return enabled;
}
-class JavaSystemPropertyListener {
- public:
- JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
- mCallback(env->NewGlobalRef(javaCallback)),
- mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
- mListenerThread = std::thread([this]() mutable {
- JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
- while (!mCleanupSignal.load()) {
- using namespace std::chrono_literals;
- // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
- // be destroyed.
- std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
- if (newVal != "" && mLastVal != newVal) {
- threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
- mLastVal = std::move(newVal);
- }
- }
- });
- }
-
- ~JavaSystemPropertyListener() {
- mCleanupSignal.store(true);
- mListenerThread.join();
- JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
- env->DeleteGlobalRef(mCallback);
- }
-
- private:
- jobject mCallback;
- android::base::CachedProperty mCachedProperty;
- std::thread mListenerThread;
- std::atomic<bool> mCleanupSignal{false};
- std::string mLastVal = "";
-};
-
-std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
-std::mutex gSysPropLock{};
-
-static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
- jstring sysProp,
- jobject javaCallback) {
- ScopedUtfChars sysPropChars{env, sysProp};
- auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
- std::string{sysPropChars.c_str()});
- std::unique_lock _l{gSysPropLock};
- gSystemPropertyListeners.push_back(std::move(listener));
-}
-
-
// ----------------------------------------------------------------------------
#define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3593,12 +3534,7 @@
android_media_AudioSystem_clearPreferredMixerAttributes),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
- MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
- MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
- "(Ljava/lang/String;Ljava/lang/Runnable;)V",
- android_media_AudioSystem_listenForSystemPropertyChange),
-
- };
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
static const JNINativeMethod gEventHandlerMethods[] =
{MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3880,12 +3816,6 @@
gAudioMixerAttributesField.mMixerBehavior =
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
- jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
- gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
- gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
-
- LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
-
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index d11166f..e874163 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,18 +16,22 @@
#define LOG_TAG "InputChannel-JNI"
-#include "android-base/stringprintf.h"
-#include <nativehelper/JNIHelp.h>
-#include "nativehelper/scoped_utf_chars.h"
+#include "android_view_InputChannel.h"
+
#include <android_runtime/AndroidRuntime.h>
#include <binder/Parcel.h>
-#include <utils/Log.h>
+#include <com_android_input_flags.h>
#include <input/InputTransport.h>
-#include "android_view_InputChannel.h"
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+#include "android-base/stringprintf.h"
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
-
#include "core_jni_helpers.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+namespace input_flags = com::android::input::flags;
namespace android {
@@ -69,6 +73,9 @@
}
void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
+ if (input_flags::remove_input_channel_from_windowstate()) {
+ return;
+ }
mDisposeCallback = callback;
mDisposeData = data;
}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d277169..41696df 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -63,7 +63,6 @@
"-c fa",
],
static_libs: [
- "A11yChecker",
"collector-device-lib-platform",
"frameworks-base-testutils",
"core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 033ac7c..c5b75ff 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,7 @@
package android.view;
import static android.util.SequenceUtils.getInitSeq;
+import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -494,8 +495,8 @@
0, displayInfo, new DisplayAdjustments());
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
- boolean result = viewRootImpl.performHapticFeedback(
- HapticFeedbackConstants.CONTEXT_CLICK, true, false /* fromIme */);
+ boolean result = viewRootImpl.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK,
+ FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
assertThat(result).isFalse();
}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
deleted file mode 100644
index 872a180..0000000
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Android Accessibility Framework owners
-include /core/java/android/view/accessibility/a11ychecker/OWNERS
-include /services/accessibility/OWNERS
-
-yaraabdullatif@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 196f89d..df80946 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -107,4 +107,24 @@
* @param pilferCallback the callback to pilfer pointers.
*/
void setPilferPointerCallback(Runnable pilferCallback);
+
+ /**
+ * Set a callback to requestTopUi.
+ * @param topUiRequest the callback to requestTopUi.
+ */
+ void setTopUiRequestCallback(TopUiRequest topUiRequest);
+
+ /**
+ * Callback to request SysUi to call
+ * {@link android.app.IActivityManager#setHasTopUi(boolean)}.
+ */
+ interface TopUiRequest {
+
+ /**
+ * Request {@link android.app.IActivityManager#setHasTopUi(boolean)} to be called.
+ * @param requestTopUi whether topUi should be requested or not
+ * @param tag tag of the request-source
+ */
+ void requestTopUi(boolean requestTopUi, String tag);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 8467e97..bb239ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -179,6 +179,7 @@
@BackNavigationInfo.BackTargetType
private int mPreviousNavigationType;
private Runnable mPilferPointerCallback;
+ private BackAnimation.TopUiRequest mRequestTopUiCallback;
public BackAnimationController(
@NonNull ShellInit shellInit,
@@ -357,6 +358,11 @@
mPilferPointerCallback = callback;
});
}
+
+ @Override
+ public void setTopUiRequestCallback(TopUiRequest topUiRequest) {
+ mShellExecutor.execute(() -> mRequestTopUiCallback = topUiRequest);
+ }
}
private static class IBackAnimationImpl extends IBackAnimation.Stub
@@ -557,6 +563,7 @@
if (!mShellBackAnimationRegistry.startGesture(backType)) {
mActiveCallback = null;
}
+ requestTopUi(true, backType);
tryPilferPointers();
} else {
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
@@ -906,6 +913,7 @@
mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
+ requestTopUi(false, mPreviousNavigationType);
}
}
@@ -969,6 +977,13 @@
}
}
+ private void requestTopUi(boolean hasTopUi, int backType) {
+ if (mRequestTopUiCallback != null && (backType == BackNavigationInfo.TYPE_CROSS_TASK
+ || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+ mRequestTopUiCallback.requestTopUi(hasTopUi, TAG);
+ }
+ }
+
/**
* Validate animation targets.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 4f04c5c..4e0c82b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -61,8 +61,7 @@
private val context: Context,
private val background: BackAnimationBackground,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- protected val transaction: SurfaceControl.Transaction,
- private val choreographer: Choreographer
+ protected val transaction: SurfaceControl.Transaction
) : ShellBackAnimation() {
protected val startClosingRect = RectF()
@@ -269,7 +268,9 @@
.setSpring(postCommitFlingSpring)
flingAnimation.start()
// do an animation-frame immediately to prevent idle frame
- flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
+ flingAnimation.doAnimationFrame(
+ Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS
+ )
val valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -362,7 +363,7 @@
}
protected fun applyTransaction() {
- transaction.setFrameTimelineVsync(choreographer.vsyncId)
+ transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
transaction.apply()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 103a654..e2b0513 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -52,7 +52,6 @@
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -69,7 +68,6 @@
* IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
* launcher starts.
*/
-@ShellMainThread
public class CrossTaskBackAnimation extends ShellBackAnimation {
private static final int BACKGROUNDCOLOR = 0x43433A;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index e266e2c..b02f97b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -19,7 +19,6 @@
import android.graphics.Rect
import android.graphics.RectF
import android.util.MathUtils
-import android.view.Choreographer
import android.view.SurfaceControl
import android.view.animation.Animation
import android.view.animation.Transformation
@@ -31,27 +30,23 @@
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
-import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
/** Class that handles customized predictive cross activity back animations. */
-@ShellMainThread
class CustomCrossActivityBackAnimation(
context: Context,
background: BackAnimationBackground,
rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
transaction: SurfaceControl.Transaction,
- choreographer: Choreographer,
private val customAnimationLoader: CustomAnimationLoader
) :
CrossActivityBackAnimation(
context,
background,
rootTaskDisplayAreaOrganizer,
- transaction,
- choreographer
+ transaction
) {
private var enterAnimation: Animation? = null
@@ -70,7 +65,6 @@
background,
rootTaskDisplayAreaOrganizer,
SurfaceControl.Transaction(),
- Choreographer.getInstance(),
CustomAnimationLoader(
TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 3b5eb36..c747e1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -16,18 +16,15 @@
package com.android.wm.shell.back
import android.content.Context
-import android.view.Choreographer
import android.view.SurfaceControl
import android.window.BackEvent
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
-import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.max
/** Class that defines cross-activity animation. */
-@ShellMainThread
class DefaultCrossActivityBackAnimation
@Inject
constructor(
@@ -39,8 +36,7 @@
context,
background,
rootTaskDisplayAreaOrganizer,
- SurfaceControl.Transaction(),
- Choreographer.getInstance()
+ SurfaceControl.Transaction()
) {
private val postCommitInterpolator = Interpolators.EMPHASIZED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index ca05864..247cc42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -88,14 +88,17 @@
/** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- displayData.keyIterator().forEach { displayId ->
- val visibleTasksCount = getVisibleTaskCount(displayId)
+ displayData.keyIterator().forEach {
executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
}
}
}
+ /** Returns a list of all [DisplayData]. */
+ private fun displayDataList(): Sequence<DisplayData> =
+ displayData.valueIterator().asSequence()
+
/**
* Add a Consumer which will inform other classes of changes to exclusion regions for all
* Desktop tasks.
@@ -208,37 +211,17 @@
return removed
}
- /** Check if a task with the given [taskId] was marked as an active task */
- fun isActiveTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.activeTasks.contains(taskId)
- }
- }
-
- /** Check if a task with the given [taskId] was marked as a closing task */
- fun isClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }
-
- /** Whether a task is visible. */
- fun isVisibleTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.visibleTasks.contains(taskId)
- }
- }
-
- /** Return whether the given Task is minimized. */
- fun isMinimizedTask(taskId: Int): Boolean {
- return displayData.valueIterator().asSequence().any { data ->
- data.minimizedTasks.contains(taskId)
- }
- }
+ fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
+ fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
+ fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
+ fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
/**
* Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
* on its display
*/
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- displayData.valueIterator().asSequence().any { data ->
+ displayDataList().any { data ->
data.visibleTasks
.subtract(data.closingTasks)
.subtract(data.minimizedTasks)
@@ -255,12 +238,6 @@
ArraySet(displayData[displayId]?.minimizedTasks)
/**
- * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks
- * are visible.
- */
- fun isDesktopModeShowing(displayId: Int): Boolean = getVisibleTaskCount(displayId) > 0
-
- /**
* Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
* ordered from front to back.
*/
@@ -305,14 +282,14 @@
return
}
- val prevCount = getVisibleTaskCount(displayId)
+ val prevCount = visibleTaskCount(displayId)
if (visible) {
displayData.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
displayData[displayId]?.visibleTasks?.remove(taskId)
}
- val newCount = getVisibleTaskCount(displayId)
+ val newCount = visibleTaskCount(displayId)
// Check if count changed
if (prevCount != newCount) {
@@ -340,7 +317,7 @@
}
/** Get number of tasks that are marked as visible on given [displayId] */
- fun getVisibleTaskCount(displayId: Int): Int {
+ fun visibleTaskCount(displayId: Int): Int {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount= %d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d46b2d6..9a1a8a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -246,10 +246,12 @@
}
}
- /** Get number of tasks that are marked as visible */
- fun getVisibleTaskCount(displayId: Int): Int {
- return desktopModeTaskRepository.getVisibleTaskCount(displayId)
- }
+ /** Gets number of visible tasks in [displayId]. */
+ fun visibleTaskCount(displayId: Int): Int =
+ desktopModeTaskRepository.visibleTaskCount(displayId)
+
+ /** Returns true if any tasks are visible in Desktop Mode. */
+ fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
/** Enter desktop by using the focused task in given `displayId` */
fun moveFocusedTaskToDesktop(displayId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -981,7 +983,7 @@
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
return null
}
- if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (!isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: bring desktop tasks to front on transition" +
@@ -1012,7 +1014,7 @@
transition: IBinder
): WindowContainerTransaction? {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
- if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
+ if (isDesktopModeShowing(task.displayId)) {
ProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: switch fullscreen task to freeform on transition" +
@@ -1396,8 +1398,7 @@
onFinishCallback: Consumer<Boolean>
): Boolean {
// TODO(b/320797628): Pass through which display we are dropping onto
- val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
- if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
// Not currently in desktop mode, ignore the drop
return false
}
@@ -1556,8 +1557,8 @@
val result = IntArray(1)
executeRemoteCallWithTaskPermission(
controller,
- "getVisibleTaskCount",
- { controller -> result[0] = controller.getVisibleTaskCount(displayId) },
+ "visibleTaskCount",
+ { controller -> result[0] = controller.visibleTaskCount(displayId) },
true /* blocking */
)
return result[0]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ff40d4c..8d63ff2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1980,12 +1980,6 @@
}
clearContentOverlay();
}
- if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
- // Avoid double removal, which is fatal.
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: trying to remove overlay (%s) while in UNDEFINED state", TAG, surface);
- return;
- }
if (surface == null || !surface.isValid()) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: trying to remove invalid content overlay (%s)", TAG, surface);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4104234..9bcd9b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -3573,7 +3573,8 @@
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
- pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
+ pw.println(innerPrefix + "isLeftRightSplit="
+ + (mSplitLayout != null ? mSplitLayout.isLeftRightSplit() : "null"));
pw.println(innerPrefix + "MainStage");
pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3585,7 +3586,9 @@
mSideStage.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
- mSplitLayout.dump(pw, childPrefix);
+ if (mSplitLayout != null) {
+ mSplitLayout.dump(pw, childPrefix);
+ }
if (!mPausingTasks.isEmpty()) {
pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4f4b809..766a6b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -353,7 +353,7 @@
return this::setRecentsTransitionDuringKeyguard;
} else if (mDesktopTasksController != null
// Check on the default display. Recents/gesture nav is only available there
- && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+ && mDesktopTasksController.visibleTaskCount(DEFAULT_DISPLAY) > 0) {
return this::setRecentsTransitionDuringDesktop;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 8bf0111..080ad90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -25,7 +25,6 @@
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.Choreographer
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
@@ -37,8 +36,6 @@
import com.android.internal.policy.TransitionAnimation
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
import junit.framework.TestCase.assertEquals
import org.junit.Assert
import org.junit.Before
@@ -50,12 +47,13 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
@SmallTest
@TestableLooper.RunWithLooper
@@ -82,7 +80,6 @@
backAnimationBackground,
rootTaskDisplayAreaOrganizer,
transaction,
- mock(Choreographer::class.java),
customAnimationLoader
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 6612aee..18b08bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -337,65 +337,65 @@
}
@Test
- fun getVisibleTaskCount() {
+ fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
// No tasks, count is 0
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
- assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_multipleDisplays() {
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -494,28 +494,6 @@
}
@Test
- fun isDesktopModeShowing_noActiveTasks_returnsFalse() {
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
- repo.addActiveTask(displayId = 0, taskId = 1)
- repo.addActiveTask(displayId = 0, taskId = 2)
- repo.updateVisibleFreeformTasks(displayId = 0, taskId = 1, visible = true)
-
- assertThat(repo.isDesktopModeShowing(displayId = 0)).isTrue()
- }
-
- @Test
fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index c56671a..da88686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -311,6 +311,31 @@
}
@Test
+ fun isDesktopModeShowing_noTasks_returnsFalse() {
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
@@ -526,32 +551,32 @@
}
@Test
- fun getVisibleTaskCount_noTasks_returnsZero() {
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ fun visibleTaskCount_noTasks_returnsZero() {
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
}
@Test
- fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskHidden)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
}
@Test
- fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
setUpHomeTask()
setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 52b5ff7..d148afd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2649,11 +2649,4 @@
* @hide
*/
public static native boolean isBluetoothVariableLatencyEnabled();
-
- /**
- * Register a native listener for system property sysprop
- * @param callback the listener which fires when the property changes
- * @hide
- */
- public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d42b256..d20b7f0 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -762,14 +762,14 @@
void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
- oneway void startLoudnessCodecUpdates(int sessionId);
+ void startLoudnessCodecUpdates(int sessionId);
- oneway void stopLoudnessCodecUpdates(int sessionId);
+ void stopLoudnessCodecUpdates(int sessionId);
- oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
in LoudnessCodecInfo codecInfo);
- oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
+ void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo);
diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
index 6e6512a..e76aeb0 100644
--- a/nfc/java/android/nfc/AvailableNfcAntenna.java
+++ b/nfc/java/android/nfc/AvailableNfcAntenna.java
@@ -28,13 +28,13 @@
public final class AvailableNfcAntenna implements Parcelable {
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
private final int mLocationX;
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
private final int mLocationY;
@@ -46,7 +46,7 @@
/**
* Location of the antenna on the X axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
public int getLocationX() {
@@ -55,7 +55,7 @@
/**
* Location of the antenna on the Y axis in millimeters.
- * 0 is the bottom-left when the user is facing the screen
+ * 0 is the top-left when the user is facing the screen
* and the device orientation is Portrait.
*/
public int getLocationY() {
diff --git a/nfc/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
index b002ca2..c57b2e0 100644
--- a/nfc/java/android/nfc/NfcAntennaInfo.java
+++ b/nfc/java/android/nfc/NfcAntennaInfo.java
@@ -64,9 +64,9 @@
/**
* Whether the device is foldable. When the device is foldable,
- * the 0, 0 is considered to be bottom-left when the device is unfolded and
+ * the 0, 0 is considered to be top-left when the device is unfolded and
* the screens are facing the user. For non-foldable devices 0, 0
- * is bottom-left when the user is facing the screen.
+ * is top-left when the user is facing the screen.
*/
public boolean isDeviceFoldable() {
return mDeviceFoldable;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
new file mode 100644
index 0000000..2eaa804
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManagerExt.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothCallback] device profile connection state change events */
+val BluetoothEventManager.onProfileConnectionStateChanged: Flow<ProfileConnectionState>
+ get() = callbackFlow {
+ val callback =
+ object : BluetoothCallback {
+ override fun onProfileConnectionStateChanged(
+ cachedDevice: CachedBluetoothDevice,
+ @BluetoothCallback.ConnectionState state: Int,
+ bluetoothProfile: Int
+ ) {
+ launch { send(ProfileConnectionState(cachedDevice, state, bluetoothProfile)) }
+ }
+ }
+ registerCallback(callback)
+ awaitClose { unregisterCallback(callback) }
+ }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
new file mode 100644
index 0000000..91a99ae
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import com.android.internal.util.ConcurrentUtils
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothLeBroadcastAssistant.Callback] source connected/removed events */
+val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow<Unit>
+ get() = callbackFlow {
+ val callback =
+ object : BluetoothLeBroadcastAssistant.Callback {
+ override fun onReceiveStateChanged(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ state: BluetoothLeBroadcastReceiveState
+ ) {
+ if (BluetoothUtils.isConnected(state)) {
+ launch { send(Unit) }
+ }
+ }
+
+ override fun onSourceRemoved(sink: BluetoothDevice, sourceId: Int, reason: Int) {
+ launch { send(Unit) }
+ }
+
+ override fun onSearchStarted(reason: Int) {}
+
+ override fun onSearchStartFailed(reason: Int) {}
+
+ override fun onSearchStopped(reason: Int) {}
+
+ override fun onSearchStopFailed(reason: Int) {}
+
+ override fun onSourceFound(source: BluetoothLeBroadcastMetadata) {}
+
+ override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+ override fun onSourceAddFailed(
+ sink: BluetoothDevice,
+ source: BluetoothLeBroadcastMetadata,
+ reason: Int
+ ) {}
+
+ override fun onSourceModified(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+
+ override fun onSourceModifyFailed(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ reason: Int
+ ) {}
+
+ override fun onSourceRemoveFailed(
+ sink: BluetoothDevice,
+ sourceId: Int,
+ reason: Int
+ ) {}
+ }
+ registerServiceCallBack(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ callback,
+ )
+ awaitClose { unregisterServiceCallBack(callback) }
+ }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.kt
new file mode 100644
index 0000000..45aaa66
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/ProfileConnectionState.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.settingslib.bluetooth
+
+data class ProfileConnectionState(
+ val cachedDevice: CachedBluetoothDevice,
+ @BluetoothCallback.ConnectionState val state: Int,
+ val bluetoothProfile: Int,
+)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 9dbf23e..eb33a7a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -16,33 +16,84 @@
package com.android.settingslib.volume.data.repository
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothCsipSetCoordinator
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
+import androidx.annotation.IntRange
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
+import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
import com.android.settingslib.flags.Flags
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+typealias GroupIdToVolumes = Map<Int, Int>
/** Provides audio sharing functionality. */
interface AudioSharingRepository {
/** Whether the device is in audio sharing. */
val inAudioSharing: Flow<Boolean>
+
+ /** The secondary headset groupId in audio sharing. */
+ val secondaryGroupId: StateFlow<Int>
+
+ /** The headset groupId to volume map during audio sharing. */
+ val volumeMap: StateFlow<GroupIdToVolumes>
+
+ /** Set the volume of secondary headset during audio sharing. */
+ suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ )
+
+ companion object {
+ const val AUDIO_SHARING_VOLUME_MIN = 0
+ const val AUDIO_SHARING_VOLUME_MAX = 255
+ }
}
+@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingRepositoryImpl(
- private val localBluetoothManager: LocalBluetoothManager?,
- backgroundCoroutineContext: CoroutineContext,
+ private val context: Context,
+ private val contentResolver: ContentResolver,
+ private val btManager: LocalBluetoothManager?,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
) : AudioSharingRepository {
override val inAudioSharing: Flow<Boolean> =
if (Flags.enableLeAudioSharing()) {
- localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
+ btManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
callbackFlow {
val listener =
object : BluetoothLeBroadcast.Callback {
@@ -92,9 +143,117 @@
flowOf(false)
}
+ private val primaryChange: Flow<Unit> = callbackFlow {
+ val callback =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ launch { send(Unit) }
+ }
+ }
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
+ false,
+ callback)
+ awaitClose { contentResolver.unregisterContentObserver(callback) }
+ }
+
+ override val secondaryGroupId: StateFlow<Int> =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ merge(
+ btManager
+ ?.profileManager
+ ?.leAudioBroadcastAssistantProfile
+ ?.onSourceConnectedOrRemoved
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ btManager
+ ?.eventManager
+ ?.onProfileConnectionStateChanged
+ ?.filter { profileConnection ->
+ profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+ profileConnection.bluetoothProfile ==
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ }
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ primaryChange.map { getSecondaryGroupId() })
+ .onStart { emit(getSecondaryGroupId()) }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ emptyFlow()
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
+
+ override val volumeMap: StateFlow<GroupIdToVolumes> =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ btManager?.profileManager?.volumeControlProfile?.let { volumeControl ->
+ inAudioSharing.flatMapLatest { isSharing ->
+ if (isSharing) {
+ callbackFlow {
+ val callback =
+ object : BluetoothVolumeControl.Callback {
+ override fun onDeviceVolumeChanged(
+ device: BluetoothDevice,
+ @IntRange(
+ from = AUDIO_SHARING_VOLUME_MIN.toLong(),
+ to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {
+ launch { send(Pair(device, volume)) }
+ }
+ }
+ // Once registered, we will receive the initial volume of all
+ // connected BT devices on VolumeControlProfile via callbacks
+ volumeControl.registerCallback(
+ ConcurrentUtils.DIRECT_EXECUTOR, callback)
+ awaitClose { volumeControl.unregisterCallback(callback) }
+ }
+ .runningFold(emptyMap<Int, Int>()) { acc, value ->
+ val groupId =
+ BluetoothUtils.getGroupId(
+ btManager.cachedDeviceManager?.findDevice(value.first))
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ acc + Pair(groupId, value.second)
+ } else {
+ acc
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ emptyFlow()
+ }
+ }
+ } ?: emptyFlow()
+ } else {
+ emptyFlow()
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+
+ override suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {
+ withContext(backgroundCoroutineContext) {
+ if (Flags.volumeDialogAudioSharingFix()) {
+ btManager?.profileManager?.volumeControlProfile?.let {
+ // Find secondary headset and set volume.
+ val cachedDevice =
+ BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager)
+ if (cachedDevice != null) {
+ it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
+ }
+ }
+ }
+ }
+ }
+
private fun isBroadcasting(): Boolean {
return Flags.enableLeAudioSharing() &&
- (localBluetoothManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null)
- ?: false)
+ (btManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null) ?: false)
+ }
+
+ private fun getSecondaryGroupId(): Int {
+ return BluetoothUtils.getGroupId(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager))
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 1c80ef4..000664d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -16,15 +16,33 @@
package com.android.settingslib.volume.data.repository
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastAssistant
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import android.bluetooth.BluetoothProfile
+import android.bluetooth.BluetoothVolumeControl
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
import com.android.settingslib.flags.Flags
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,6 +57,9 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -52,27 +73,76 @@
@RunWith(AndroidJUnit4::class)
class AudioSharingRepositoryTest {
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
- @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
- @Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager
- @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+ @Mock private lateinit var btManager: LocalBluetoothManager
+
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+
+ @Mock private lateinit var broadcast: LocalBluetoothLeBroadcast
+
+ @Mock private lateinit var assistant: LocalBluetoothLeBroadcastAssistant
+
+ @Mock private lateinit var volumeControl: VolumeControlProfile
+
+ @Mock private lateinit var eventManager: BluetoothEventManager
+
+ @Mock private lateinit var deviceManager: CachedBluetoothDeviceManager
+
+ @Mock private lateinit var device1: BluetoothDevice
+
+ @Mock private lateinit var device2: BluetoothDevice
+
+ @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+
+ @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+
+ @Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
+
+ @Mock private lateinit var contentResolver: ContentResolver
@Captor
- private lateinit var leBroadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
- private val testScope = TestScope()
+ private lateinit var broadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
+ @Captor
+ private lateinit var assistantCallbackCaptor:
+ ArgumentCaptor<BluetoothLeBroadcastAssistant.Callback>
+
+ @Captor private lateinit var btCallbackCaptor: ArgumentCaptor<BluetoothCallback>
+
+ @Captor private lateinit var contentObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor
+ private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback>
+
+ private val testScope = TestScope()
+ private val context: Context = ApplicationProvider.getApplicationContext()
private lateinit var underTest: AudioSharingRepository
@Before
fun setup() {
- `when`(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
- `when`(localBluetoothProfileManager.leAudioBroadcastProfile)
- .thenReturn(localBluetoothLeBroadcast)
- `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(true)
+ `when`(btManager.profileManager).thenReturn(profileManager)
+ `when`(profileManager.leAudioBroadcastProfile).thenReturn(broadcast)
+ `when`(profileManager.leAudioBroadcastAssistantProfile).thenReturn(assistant)
+ `when`(profileManager.volumeControlProfile).thenReturn(volumeControl)
+ `when`(btManager.eventManager).thenReturn(eventManager)
+ `when`(btManager.cachedDeviceManager).thenReturn(deviceManager)
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ `when`(cachedDevice1.groupId).thenReturn(TEST_GROUP_ID1)
+ `when`(cachedDevice1.device).thenReturn(device1)
+ `when`(deviceManager.findDevice(device1)).thenReturn(cachedDevice1)
+ `when`(cachedDevice2.groupId).thenReturn(TEST_GROUP_ID2)
+ `when`(cachedDevice2.device).thenReturn(device2)
+ `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
+ `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
+ `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
underTest =
AudioSharingRepositoryImpl(
- localBluetoothManager,
+ context,
+ contentResolver,
+ btManager,
+ testScope.backgroundScope,
testScope.testScheduler,
)
}
@@ -84,9 +154,9 @@
val states = mutableListOf<Boolean?>()
underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
runCurrent()
- triggerAudioSharingStateChange(false)
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
runCurrent()
- triggerAudioSharingStateChange(true)
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_START, broadcastStarted)
runCurrent()
Truth.assertThat(states).containsExactly(true, false, true)
@@ -102,19 +172,229 @@
runCurrent()
Truth.assertThat(states).containsExactly(false)
- verify(localBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any())
- verify(localBluetoothLeBroadcast, never()).isEnabled(any())
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(broadcast, never()).isEnabled(any())
}
}
- private fun triggerAudioSharingStateChange(inAudioSharing: Boolean) {
- verify(localBluetoothLeBroadcast)
- .registerServiceCallBack(any(), leBroadcastCallbackCaptor.capture())
- `when`(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(inAudioSharing)
- if (inAudioSharing) {
- leBroadcastCallbackCaptor.value.onBroadcastStarted(0, 0)
- } else {
- leBroadcastCallbackCaptor.value.onBroadcastStopped(0, 0)
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun secondaryGroupIdChange_emitValues() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerSourceAdded()
+ runCurrent()
+ triggerContentObserverChange()
+ runCurrent()
+ triggerSourceRemoved()
+ runCurrent()
+ triggerSourceAdded()
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO)
+ runCurrent()
+ triggerProfileConnectionChange(
+ BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ runCurrent()
+
+ Truth.assertThat(groupIds)
+ .containsExactly(
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2,
+ TEST_GROUP_ID1,
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2,
+ TEST_GROUP_ID_INVALID)
}
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun secondaryGroupIdChange_audioSharingFlagOff_returnFalse() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
+ verify(assistant, never()).registerServiceCallBack(any(), any())
+ verify(eventManager, never()).registerCallback(any())
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun volumeMapChange_emitValues() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerVolumeMapChange(Pair(device1, TEST_VOLUME1))
+ runCurrent()
+ triggerVolumeMapChange(Pair(device1, TEST_VOLUME2))
+ runCurrent()
+ triggerAudioSharingStateChange(TriggerType.BROADCAST_STOP, broadcastStopped)
+ runCurrent()
+ verify(volumeControl).unregisterCallback(any())
+ runCurrent()
+
+ Truth.assertThat(volumeMaps)
+ .containsExactly(
+ emptyMap<Int, Int>(),
+ mapOf(TEST_GROUP_ID1 to TEST_VOLUME1),
+ mapOf(TEST_GROUP_ID1 to TEST_VOLUME2))
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun volumeMapChange_audioSharingFlagOff_returnFalse() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(volumeMaps).isEmpty()
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(volumeControl, never()).registerCallback(any(), any())
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun setSecondaryVolume_setValue() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true)
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
+ fun setSecondaryVolume_audioSharingFlagOff_doNothing() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
+ }
+ }
+
+ private fun triggerAudioSharingStateChange(
+ type: TriggerType,
+ broadcastAction: BluetoothLeBroadcast.Callback.() -> Unit
+ ) {
+ verify(broadcast).registerServiceCallBack(any(), broadcastCallbackCaptor.capture())
+ when (type) {
+ TriggerType.BROADCAST_START -> {
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ broadcastCallbackCaptor.value.broadcastAction()
+ }
+ TriggerType.BROADCAST_STOP -> {
+ `when`(broadcast.isEnabled(null)).thenReturn(false)
+ broadcastCallbackCaptor.value.broadcastAction()
+ }
+ }
+ }
+
+ private fun triggerSourceAdded() {
+ verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
+ }
+
+ private fun triggerSourceRemoved() {
+ verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ assistantCallbackCaptor.value.sourceRemoved(device2)
+ }
+
+ private fun triggerProfileConnectionChange(state: Int, profile: Int) {
+ verify(eventManager).registerCallback(btCallbackCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID1)
+ btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
+ }
+
+ private fun triggerContentObserverChange() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())),
+ eq(false),
+ contentObserverCaptor.capture())
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ contentObserverCaptor.value.primaryChanged()
+ }
+
+ private fun triggerVolumeMapChange(change: Pair<BluetoothDevice, Int>) {
+ verify(volumeControl).registerCallback(any(), volumeCallbackCaptor.capture())
+ volumeCallbackCaptor.value.onDeviceVolumeChanged(change.first, change.second)
+ }
+
+ private enum class TriggerType {
+ BROADCAST_START,
+ BROADCAST_STOP
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_GROUP_ID1 = 1
+ const val TEST_GROUP_ID2 = 2
+ const val TEST_SOURCE_ID = 1
+ const val TEST_BROADCAST_ID = 1
+ const val TEST_REASON = 1
+ const val TEST_RECEIVE_STATE_CONTENT = 1L
+ const val TEST_VOLUME1 = 10
+ const val TEST_VOLUME2 = 20
+
+ val broadcastStarted: BluetoothLeBroadcast.Callback.() -> Unit = {
+ onBroadcastStarted(TEST_REASON, TEST_BROADCAST_ID)
+ }
+ val broadcastStopped: BluetoothLeBroadcast.Callback.() -> Unit = {
+ onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID)
+ }
+ val sourceAdded:
+ BluetoothLeBroadcastAssistant.Callback.(
+ sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit =
+ { sink, state ->
+ onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
+ }
+ val sourceRemoved: BluetoothLeBroadcastAssistant.Callback.(sink: BluetoothDevice) -> Unit =
+ { sink ->
+ onSourceRemoved(sink, TEST_SOURCE_ID, TEST_REASON)
+ }
+ val primaryChanged: ContentObserver.() -> Unit = { onChange(false) }
+ }
}
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 43d51c3..92f03d7 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
@@ -45,7 +45,7 @@
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.compose.Dimensions.SlideOffsetY
+import com.android.systemui.communal.ui.compose.Dimensions.Companion.SlideOffsetY
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
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 bd4710b..ef6eec8 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
@@ -16,6 +16,8 @@
package com.android.systemui.communal.ui.compose
+import android.content.Context
+import android.content.res.Configuration
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
@@ -116,6 +118,7 @@
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
@@ -129,6 +132,7 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -388,6 +392,10 @@
}
}
+val hubDimensions: Dimensions
+ @Composable
+ get() = Dimensions(LocalContext.current, LocalConfiguration.current, LocalDensity.current)
+
@Composable
private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
val colors = LocalAndroidColorScheme.current
@@ -509,7 +517,6 @@
gridState = gridState,
contentListState = contentListState,
contentOffset = contentOffset,
- updateDragPositionForRemove = updateDragPositionForRemove
)
// A full size box in background that listens to widget drops from the picker.
@@ -517,7 +524,7 @@
// for android drag events.
Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
- gridModifier = gridModifier.height(Dimensions.GridHeight)
+ gridModifier = gridModifier.height(hubDimensions.GridHeight)
}
LazyHorizontalGrid(
@@ -595,7 +602,7 @@
) {
val colors = LocalAndroidColorScheme.current
Card(
- modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
+ modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(3.dp, colors.secondary),
shape = RoundedCornerShape(size = 80.dp)
@@ -1280,7 +1287,7 @@
return PaddingValues(
start = Dimensions.ItemSpacing,
end = Dimensions.ItemSpacing,
- top = Dimensions.GridTopSpacing,
+ top = hubDimensions.GridTopSpacing,
)
}
val context = LocalContext.current
@@ -1289,7 +1296,8 @@
val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
val verticalPadding =
- ((screenHeight - toolbarHeight - Dimensions.GridHeight + Dimensions.GridTopSpacing) / 2)
+ ((screenHeight - toolbarHeight - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) /
+ 2)
.coerceAtLeast(Dimensions.Spacing)
return PaddingValues(
start = Dimensions.ToolbarPaddingHorizontal,
@@ -1345,29 +1353,44 @@
fun toOffset(): Offset = Offset(start, top)
}
-object Dimensions {
- val CardHeightFull = 530.dp
- val GridTopSpacing = 114.dp
- val GridHeight = CardHeightFull + GridTopSpacing
- val ItemSpacing = 50.dp
- val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
- val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
- val CardWidth = 360.dp
- val CardOutlineWidth = 3.dp
- val Spacing = ItemSpacing / 2
+class Dimensions(val context: Context, val config: Configuration, val density: Density) {
+ val GridTopSpacing: Dp
+ get() {
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return 114.dp
+ } else {
+ val windowMetrics =
+ WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
- // The sizing/padding of the toolbar in glanceable hub edit mode
- val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = ItemSpacing
- val ToolbarButtonPaddingHorizontal = 24.dp
- val ToolbarButtonPaddingVertical = 16.dp
- val ButtonPadding =
- PaddingValues(
- vertical = ToolbarButtonPaddingVertical,
- horizontal = ToolbarButtonPaddingHorizontal,
- )
- val IconSize = 40.dp
- val SlideOffsetY = 30.dp
+ return (screenHeight - CardHeightFull) / 2
+ }
+ }
+
+ val GridHeight = CardHeightFull + GridTopSpacing
+
+ companion object {
+ val CardHeightFull = 530.dp
+ val ItemSpacing = 50.dp
+ val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+ val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+ val CardWidth = 360.dp
+ val CardOutlineWidth = 3.dp
+ val Spacing = ItemSpacing / 2
+
+ // The sizing/padding of the toolbar in glanceable hub edit mode
+ val ToolbarPaddingTop = 27.dp
+ val ToolbarPaddingHorizontal = ItemSpacing
+ val ToolbarButtonPaddingHorizontal = 24.dp
+ val ToolbarButtonPaddingVertical = 16.dp
+ val ButtonPadding =
+ PaddingValues(
+ vertical = ToolbarButtonPaddingVertical,
+ horizontal = ToolbarButtonPaddingHorizontal,
+ )
+ val IconSize = 40.dp
+ val SlideOffsetY = 30.dp
+ }
}
private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 9e6f22a..0c29394 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -18,17 +18,13 @@
import android.content.ClipDescription
import android.view.DragEvent
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
-import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
@@ -45,8 +41,7 @@
import com.android.systemui.communal.util.WidgetPickerIntentUtils
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
@@ -59,32 +54,22 @@
gridState: LazyGridState,
contentOffset: Offset,
contentListState: ContentListState,
- updateDragPositionForRemove: (offset: Offset) -> Boolean,
): DragAndDropTargetState {
val scope = rememberCoroutineScope()
- val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
- // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
- // that allows differentiating intention of scrolling from intention of dragging over the first
- // visible item.
val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
val state =
- remember(gridState, contentListState) {
+ remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) {
DragAndDropTargetState(
state = gridState,
contentOffset = contentOffset,
contentListState = contentListState,
- scope = scope,
- autoScrollSpeed = autoScrollSpeed,
autoScrollThreshold = autoScrollThreshold,
- updateDragPositionForRemove = updateDragPositionForRemove,
+ scope = scope,
)
}
- LaunchedEffect(autoScrollSpeed.floatValue) {
- if (autoScrollSpeed.floatValue != 0f) {
- while (isActive) {
- gridState.scrollBy(autoScrollSpeed.floatValue)
- delay(10)
- }
+ LaunchedEffect(state) {
+ for (diff in state.scrollChannel) {
+ gridState.scrollBy(diff)
}
}
return state
@@ -96,7 +81,6 @@
* @see androidx.compose.foundation.draganddrop.dragAndDropTarget
* @see DragEvent
*/
-@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun Modifier.dragAndDropTarget(
dragDropTargetState: DragAndDropTargetState,
@@ -122,6 +106,10 @@
return state.onDrop(event)
}
+ override fun onExited(event: DragAndDropEvent) {
+ state.onExited()
+ }
+
override fun onEnded(event: DragAndDropEvent) {
state.onEnded()
}
@@ -149,19 +137,17 @@
private val state: LazyGridState,
private val contentOffset: Offset,
private val contentListState: ContentListState,
- private val scope: CoroutineScope,
- private val autoScrollSpeed: MutableState<Float>,
private val autoScrollThreshold: Float,
- private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+ private val scope: CoroutineScope,
) {
/**
* The placeholder item that is treated as if it is being dragged across the grid. It is added
* to grid once drag and drop event is started and removed when event ends.
*/
private var placeHolder = CommunalContentModel.WidgetPlaceholder()
-
private var placeHolderIndex: Int? = null
- private var isOnRemoveButton = false
+
+ internal val scrollChannel = Channel<Float>()
fun onStarted() {
// assume item will be added to the end.
@@ -170,39 +156,39 @@
}
fun onMoved(event: DragAndDropEvent) {
- val dragEvent = event.toAndroidDragEvent()
- isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
- if (!isOnRemoveButton) {
- findTargetItem(dragEvent)?.apply {
- var scrollIndex: Int? = null
- var scrollOffset: Int? = null
- if (placeHolderIndex == state.firstVisibleItemIndex) {
- // Save info about the first item before the move, to neutralize the automatic
- // keeping first item first.
- scrollIndex = placeHolderIndex
- scrollOffset = state.firstVisibleItemScrollOffset
- }
+ val dragOffset = event.toOffset()
- autoScrollIfNearEdges(dragEvent)
+ val targetItem =
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .firstItemAtOffset(dragOffset - contentOffset)
- if (contentListState.isItemEditable(this.index)) {
- movePlaceholderTo(this.index)
- placeHolderIndex = this.index
- }
-
- if (scrollIndex != null && scrollOffset != null) {
- // this is needed to neutralize automatic keeping the first item first.
- scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
- }
+ if (targetItem != null) {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
}
+
+ if (contentListState.isItemEditable(targetItem.index)) {
+ movePlaceholderTo(targetItem.index)
+ placeHolderIndex = targetItem.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ } else {
+ computeAutoscroll(dragOffset).takeIf { it != 0f }?.let { scrollChannel.trySend(it) }
}
}
fun onDrop(event: DragAndDropEvent): Boolean {
- autoScrollSpeed.value = 0f
- if (isOnRemoveButton) {
- return false
- }
return placeHolderIndex?.let { dropIndex ->
val widgetExtra = event.maybeWidgetExtra() ?: return false
val (componentName, user) = widgetExtra
@@ -221,39 +207,35 @@
}
fun onEnded() {
- autoScrollSpeed.value = 0f
placeHolderIndex = null
contentListState.list.remove(placeHolder)
- isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
}
- private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ fun onExited() {
+ onEnded()
+ }
+
+ private fun computeAutoscroll(dragOffset: Offset): Float {
val orientation = state.layoutInfo.orientation
val distanceFromStart =
if (orientation == Orientation.Horizontal) {
- dragEvent.x
+ dragOffset.x
} else {
- dragEvent.y
+ dragOffset.y
}
val distanceFromEnd =
if (orientation == Orientation.Horizontal) {
- state.layoutInfo.viewportSize.width - dragEvent.x
+ state.layoutInfo.viewportEndOffset - dragOffset.x
} else {
- state.layoutInfo.viewportSize.height - dragEvent.y
+ state.layoutInfo.viewportEndOffset - dragOffset.y
}
- autoScrollSpeed.value =
- when {
- distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
- distanceFromStart < autoScrollThreshold ->
- -(autoScrollThreshold - distanceFromStart)
- else -> 0f
- }
- }
- private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
- state.layoutInfo.visibleItemsInfo.firstItemAtOffset(
- Offset(dragEvent.x, dragEvent.y) - contentOffset
- )
+ return when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold
+ else -> 0f
+ }
+ }
private fun movePlaceholderTo(index: Int) {
val currentIndex = contentListState.list.indexOf(placeHolder)
@@ -271,4 +253,6 @@
val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) }
}
+
+ private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index a184cf3..776e166 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -198,6 +198,7 @@
LaunchedEffect(scrollableState.isScrollInProgress) {
if (!scrollableState.isScrollInProgress && scrollOffset <= minScrollOffset) {
+ viewModel.setHeadsUpAnimatingAway(false)
viewModel.snoozeHun()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index f9f7df8..4f5d0e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.fakeFacePropertyRepository
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
@@ -36,7 +37,7 @@
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
@@ -75,15 +76,20 @@
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private lateinit var underTest: BouncerMessageViewModel
+ private val ignoreHelpMessageId = 1
@Before
fun setUp() {
kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(ignoreHelpMessageId)
+ )
underTest = kosmos.bouncerMessageViewModel
overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
"not mainline reboot"
)
}
@@ -379,7 +385,15 @@
runCurrent()
kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
- HelpFaceAuthenticationStatus(1, "some helpful message")
+ HelpFaceAuthenticationStatus(0, "some helpful message", 0)
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ 0,
+ "some helpful message",
+ FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS
+ )
)
runCurrent()
assertThat(bouncerMessage?.text).isEqualTo("Enter PIN")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2546f27..2bf50b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -52,7 +52,6 @@
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
-import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
@@ -79,7 +78,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.res.R
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
@@ -478,29 +476,6 @@
}
@Test
- fun faceHelpMessagesAreIgnoredBasedOnConfig() =
- testScope.runTest {
- overrideResource(
- R.array.config_face_acquire_device_entry_ignorelist,
- intArrayOf(10, 11)
- )
- underTest = createDeviceEntryFaceAuthRepositoryImpl()
- initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
-
- underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- authenticationCallback.value.onAuthenticationHelp(9, "help msg")
- authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
- authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
-
- val response = authStatus() as HelpFaceAuthenticationStatus
- assertThat(response.msg).isEqualTo("help msg")
- assertThat(response.msgId).isEqualTo(response.msgId)
- }
-
- @Test
fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
testScope.runTest {
fakeUserRepository.setSelectedUserInfo(primaryUser)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 546510b..3253edf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
-import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -32,27 +31,14 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -61,7 +47,6 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -438,231 +423,6 @@
assertThat(isUnlocked).isTrue()
}
- @Test
- fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- null,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- null,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
- testScope.runTest {
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- "not mainline reboot"
- )
- runCurrent()
-
- verifyRestrictionReasonsForAuthFlags(
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
- DeviceNotUnlockedSinceReboot,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
- AdaptiveAuthRequest,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
- BouncerLockedOut,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
- SecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
- UserLockdown,
- LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
- NonStrongBiometricsSecurityTimeout,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- UnattendedUpdate,
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
- PolicyLockdown,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
- TrustAgentDisabled,
- LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
- TrustAgentDisabled,
- )
- }
-
- @Test
- fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
- testScope.runTest {
- val deviceEntryRestrictionReason by
- collectLastValue(underTest.deviceEntryRestrictionReason)
- kosmos.fakeSystemPropertiesHelper.set(
- DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
- DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
- )
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(
- userId = 1,
- flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- )
- )
- runCurrent()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason).isNull()
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
-
- kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
- runCurrent()
-
- assertThat(deviceEntryRestrictionReason)
- .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
- }
-
- @Test
- fun reportUserPresent_whenDeviceEntered() =
- testScope.runTest {
- val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(0)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- switchToScene(Scenes.Lockscreen)
- assertThat(isDeviceEntered).isFalse()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(1)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- switchToScene(Scenes.Gone)
- assertThat(isDeviceEntered).isTrue()
- assertThat(kosmos.fakeDeviceEntryRepository.userPresentCount).isEqualTo(2)
- }
-
- private fun TestScope.verifyRestrictionReasonsForAuthFlags(
- vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
- ) {
- val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
-
- authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
- kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
- AuthenticationFlags(userId = 1, flag = flag)
- )
- runCurrent()
-
- if (expectedReason == null) {
- assertThat(deviceEntryRestrictionReason).isNull()
- } else {
- assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
- }
- }
- }
-
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index a7a7bea3..c2acc5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -19,17 +19,20 @@
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -40,6 +43,7 @@
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -54,18 +58,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationRepository = kosmos.fakeAuthenticationRepository
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- val underTest =
- DeviceUnlockedInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = kosmos.authenticationInteractor,
- deviceEntryRepository = deviceEntryRepository,
- trustInteractor = kosmos.trustInteractor,
- faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
- powerInteractor = kosmos.powerInteractor,
- )
+ val underTest = kosmos.deviceUnlockedInteractor
@Before
fun setup() {
@@ -100,6 +94,30 @@
}
@Test
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPinAndInLockdown_isFalse() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ val isInLockdown by collectLastValue(underTest.isInLockdown)
+
+ authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag =
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ )
+ )
+ runCurrent()
+ assertThat(isInLockdown).isTrue()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
+ }
+
+ @Test
fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
@@ -221,6 +239,218 @@
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_whenLockdown() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ DeviceEntryRestrictionReason.BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ DeviceEntryRestrictionReason.SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ DeviceEntryRestrictionReason.UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ DeviceEntryRestrictionReason.UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ DeviceEntryRestrictionReason.PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ DeviceEntryRestrictionReason.TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
+ DeviceUnlockedInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..142631e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+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.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioSharingInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ lateinit var underTest: AudioSharingInteractor
+
+ @Before
+ fun setUp() {
+ with(kosmos) { underTest = audioSharingInteractor }
+ }
+
+ @Test
+ fun volumeChanges_returnVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID)
+ setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isEqualTo(TEST_VOLUME)
+ }
+ }
+ }
+
+ @Test
+ fun volumeChanges_returnNull() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID_INVALID)
+ setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun volumeChanges_returnDefaultVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) {
+ setSecondaryGroupId(TEST_GROUP_ID)
+ setVolumeMap(emptyMap())
+ }
+ val volume by collectLastValue(underTest.volume)
+ runCurrent()
+
+ Truth.assertThat(volume).isEqualTo(TEST_VOLUME_DEFAULT)
+ }
+ }
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID = 1
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_VOLUME = 10
+ const val TEST_VOLUME_DEFAULT = 20
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 56273eb..6e25744 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -33,6 +33,7 @@
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.assist.AssistLogger;
@@ -66,7 +67,7 @@
protected InvocationLightsView mInvocationLightsView;
protected final AssistLogger mAssistLogger;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final MetricsLogger mMetricsLogger;
private final Lazy<AssistManager> mAssistManagerLazy;
private final WindowManager.LayoutParams mLayoutParams;
@@ -80,12 +81,12 @@
@Inject
public DefaultUiController(Context context, AssistLogger assistLogger,
- WindowManager windowManager, MetricsLogger metricsLogger,
- Lazy<AssistManager> assistManagerLazy,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy,
NavigationBarController navigationBarController) {
mAssistLogger = assistLogger;
mRoot = new FrameLayout(context);
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mMetricsLogger = metricsLogger;
mAssistManagerLazy = assistManagerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index a9f985f..468737d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -288,6 +288,7 @@
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
withContext(backgroundDispatcher) {
if (isSuccessful) {
+ lockPatternUtils.userPresent(selectedUserId)
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
_hasLockoutOccurred.value = false
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.kt
new file mode 100644
index 0000000..1685f49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDebouncer.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.biometrics
+
+import android.util.Log
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+
+/**
+ * Debounces face help messages with parameters:
+ * - window: Window of time (in milliseconds) to analyze face acquired messages)
+ * - startWindow: Window of time on start required before showing the first help message
+ * - shownFaceMessageFrequencyBoost: Frequency boost given to messages that are currently shown to
+ * the user
+ */
+class FaceHelpMessageDebouncer(
+ private val window: Long = DEFAULT_WINDOW_MS,
+ private val startWindow: Long = window,
+ private val shownFaceMessageFrequencyBoost: Int = 4,
+) {
+ private val TAG = "FaceHelpMessageDebouncer"
+ private var startTime = 0L
+ private var helpFaceAuthStatuses: MutableList<HelpFaceAuthenticationStatus> = mutableListOf()
+ private var lastMessageIdShown: Int? = null
+
+ /** Remove messages that are outside of the time [window]. */
+ private fun removeOldMessages(currTimestamp: Long) {
+ var numToRemove = 0
+ // This works under the assumption that timestamps are ordered from first to last
+ // in chronological order
+ for (index in helpFaceAuthStatuses.indices) {
+ if ((helpFaceAuthStatuses[index].createdAt + window) >= currTimestamp) {
+ break // all timestamps from here and on are within the window
+ }
+ numToRemove += 1
+ }
+
+ // Remove all outside time window
+ repeat(numToRemove) { helpFaceAuthStatuses.removeFirst() }
+
+ if (numToRemove > 0) {
+ Log.v(TAG, "removedFirst=$numToRemove")
+ }
+ }
+
+ private fun getMostFrequentHelpMessage(): HelpFaceAuthenticationStatus? {
+ // freqMap: msgId => frequency
+ val freqMap = helpFaceAuthStatuses.groupingBy { it.msgId }.eachCount().toMutableMap()
+
+ // Give shownFaceMessageFrequencyBoost to lastMessageIdShown
+ if (lastMessageIdShown != null) {
+ freqMap.computeIfPresent(lastMessageIdShown!!) { _, value ->
+ value + shownFaceMessageFrequencyBoost
+ }
+ }
+ // Go through all msgId keys & find the highest frequency msgId
+ val msgIdWithHighestFrequency =
+ freqMap.entries
+ .maxWithOrNull { (msgId1, freq1), (msgId2, freq2) ->
+ // ties are broken by more recent message
+ if (freq1 == freq2) {
+ helpFaceAuthStatuses
+ .findLast { it.msgId == msgId1 }!!
+ .createdAt
+ .compareTo(
+ helpFaceAuthStatuses.findLast { it.msgId == msgId2 }!!.createdAt
+ )
+ } else {
+ freq1.compareTo(freq2)
+ }
+ }
+ ?.key
+ return helpFaceAuthStatuses.findLast { it.msgId == msgIdWithHighestFrequency }
+ }
+
+ fun addMessage(helpFaceAuthStatus: HelpFaceAuthenticationStatus) {
+ helpFaceAuthStatuses.add(helpFaceAuthStatus)
+ Log.v(TAG, "added message=$helpFaceAuthStatus")
+ }
+
+ fun getMessageToShow(atTimestamp: Long): HelpFaceAuthenticationStatus? {
+ if (helpFaceAuthStatuses.isEmpty() || (atTimestamp - startTime) < startWindow) {
+ // there's not enough time that has passed to determine whether to show anything yet
+ Log.v(TAG, "No message; haven't made initial threshold window OR no messages")
+ return null
+ }
+ removeOldMessages(atTimestamp)
+ val messageToShow = getMostFrequentHelpMessage()
+ if (lastMessageIdShown != messageToShow?.msgId) {
+ Log.v(
+ TAG,
+ "showMessage previousLastMessageId=$lastMessageIdShown" +
+ "\n\tmessageToShow=$messageToShow " +
+ "\n\thelpFaceAuthStatusesSize=${helpFaceAuthStatuses.size}" +
+ "\n\thelpFaceAuthStatuses=$helpFaceAuthStatuses"
+ )
+ lastMessageIdShown = messageToShow?.msgId
+ }
+ return messageToShow
+ }
+
+ fun startNewFaceAuthSession(faceAuthStartedTime: Long) {
+ Log.d(TAG, "startNewFaceAuthSession at startTime=$startTime")
+ startTime = faceAuthStartedTime
+ helpFaceAuthStatuses.clear()
+ lastMessageIdShown = null
+ }
+
+ companion object {
+ const val DEFAULT_WINDOW_MS = 200L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c868d01..430887d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -94,8 +94,16 @@
val textColorError =
view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
+
+ val attributes =
+ view.context.obtainStyledAttributes(
+ R.style.TextAppearance_AuthCredential_Indicator,
+ intArrayOf(android.R.attr.textColor)
+ )
val textColorHint =
- view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ if (constraintBp()) attributes.getColor(0, 0)
+ else view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ attributes.recycle()
val logoView = view.requireViewById<ImageView>(R.id.logo)
val logoDescriptionView = view.requireViewById<TextView>(R.id.logo_description)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 6cb9b16..810b6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -32,7 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
import com.android.systemui.deviceentry.shared.model.FaceLockoutMessage
@@ -75,7 +75,7 @@
private val clock: SystemClock,
private val biometricMessageInteractor: BiometricMessageInteractor,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
) {
@@ -119,7 +119,7 @@
}
} else if (authMethod.isSecure) {
combine(
- deviceEntryInteractor.deviceEntryRestrictionReason,
+ deviceUnlockedInteractor.deviceEntryRestrictionReason,
lockoutMessage,
fingerprintInteractor.isFingerprintCurrentlyAllowedOnBouncer,
resetToDefault,
@@ -413,7 +413,7 @@
clock: SystemClock,
biometricMessageInteractor: BiometricMessageInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
+ deviceUnlockedInteractor: DeviceUnlockedInteractor,
fingerprintInteractor: DeviceEntryFingerprintAuthInteractor,
flags: ComposeBouncerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
@@ -427,7 +427,7 @@
clock = clock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = faceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = fingerprintInteractor,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index fa52dad..9460eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,16 +57,13 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
-import java.util.Arrays
import java.util.concurrent.Executor
-import java.util.stream.Collectors
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -170,7 +167,6 @@
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
private var detectCancellationSignal: CancellationSignal? = null
- private var faceAcquiredInfoIgnoreList: Set<Int>
private var retryCount = 0
private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
@@ -240,14 +236,6 @@
faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
faceAuthLogger.addLockoutResetCallbackDone()
}
- faceAcquiredInfoIgnoreList =
- Arrays.stream(
- context.resources.getIntArray(
- R.array.config_face_acquire_device_entry_ignorelist
- )
- )
- .boxed()
- .collect(Collectors.toSet())
dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
canRunFaceAuth =
@@ -485,10 +473,8 @@
}
override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
- if (faceAcquiredInfoIgnoreList.contains(code)) {
- return
- }
- _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
+ _authenticationStatus.value =
+ HelpFaceAuthenticationStatus(code, helpStr?.toString())
}
override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
@@ -731,7 +717,6 @@
pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
pw.println(" authCancellationSignal: $authCancellationSignal")
pw.println(" detectCancellationSignal: $detectCancellationSignal")
- pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
pw.println(" _detectionStatus: ${_detectionStatus.value}")
pw.println(" currentUserId: $currentUserId")
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 0f18978..e2ad774 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -39,12 +39,6 @@
* the lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean>
-
- /**
- * Reports, to system server, that the user is "present" now. This is a signal that system
- * server uses to know that the device has been entered.
- */
- suspend fun reportUserPresent()
}
/** Encapsulates application state for device entry. */
@@ -84,17 +78,6 @@
SharingStarted.Eagerly,
initialValue = keyguardBypassController.bypassEnabled,
)
-
- override suspend fun reportUserPresent() {
- withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUser.value.userInfo.id
- lockPatternUtils.userPresent(selectedUserId)
- }
- }
-
- companion object {
- private const val TAG = "DeviceEntryRepositoryImpl"
- }
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
new file mode 100644
index 0000000..34b1544
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractor.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricFaceConstants
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.res.R
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+
+/**
+ * Process face authentication statuses.
+ * - Ignores face help messages based on R.array.config_face_acquire_device_entry_ignorelist.
+ * - Uses FaceHelpMessageDebouncer to debounce flickery help messages.
+ */
+@SysUISingleton
+class DeviceEntryFaceAuthStatusInteractor
+@Inject
+constructor(
+ repository: DeviceEntryFaceAuthRepository,
+ @Main private val resources: Resources,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ private val faceHelpMessageDebouncer = FaceHelpMessageDebouncer()
+ private var faceAcquiredInfoIgnoreList: Set<Int> =
+ Arrays.stream(resources.getIntArray(R.array.config_face_acquire_device_entry_ignorelist))
+ .boxed()
+ .collect(Collectors.toSet())
+
+ val authenticationStatus: StateFlow<FaceAuthenticationStatus?> =
+ repository.authenticationStatus
+ .transform { authenticationStatus ->
+ if (authenticationStatus is AcquiredFaceAuthenticationStatus) {
+ if (
+ authenticationStatus.acquiredInfo ==
+ BiometricFaceConstants.FACE_ACQUIRED_START
+ ) {
+ faceHelpMessageDebouncer.startNewFaceAuthSession(
+ authenticationStatus.createdAt
+ )
+ }
+ }
+
+ if (authenticationStatus is HelpFaceAuthenticationStatus) {
+ if (!faceAcquiredInfoIgnoreList.contains(authenticationStatus.msgId)) {
+ faceHelpMessageDebouncer.addMessage(authenticationStatus)
+ }
+
+ val messageToShow =
+ faceHelpMessageDebouncer.getMessageToShow(
+ atTimestamp = authenticationStatus.createdAt,
+ )
+ if (messageToShow != null) {
+ emit(messageToShow)
+ }
+
+ return@transform
+ }
+
+ emit(authenticationStatus)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 425bb96..ea0e59b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,33 +16,24 @@
package com.android.systemui.deviceentry.domain.interactor
-import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
-import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.Quad
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -61,12 +52,7 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
- private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
- private val trustInteractor: TrustInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
- private val systemPropertiesHelper: SystemPropertiesHelper,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
) {
/**
@@ -109,11 +95,6 @@
false
}
}
- .onEach { isDeviceEntered ->
- if (isDeviceEntered) {
- repository.reportUserPresent()
- }
- }
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
@@ -156,70 +137,6 @@
initialValue = null,
)
- private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
- private val fingerprintEnrolledAndEnabled =
- biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
- private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
-
- private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
- combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
-
- /**
- * Reason why device entry is restricted to certain authentication methods for the current user.
- *
- * Emits null when there are no device entry restrictions active.
- */
- val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
- faceOrFingerprintOrTrustEnabled.flatMapLatest {
- (faceEnabled, fingerprintEnabled, trustEnabled) ->
- if (faceEnabled || fingerprintEnabled || trustEnabled) {
- combine(
- biometricSettingsInteractor.authenticationFlags,
- faceAuthInteractor.isLockedOut,
- fingerprintAuthInteractor.isLockedOut,
- trustInteractor.isTrustAgentCurrentlyAllowed,
- ::Quad
- )
- .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
- when {
- authFlags.isPrimaryAuthRequiredAfterReboot &&
- wasRebootedForMainlineUpdate ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
- authFlags.isPrimaryAuthRequiredAfterReboot ->
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
- authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
- DeviceEntryRestrictionReason.PolicyLockdown
- authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
- authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
- DeviceEntryRestrictionReason.UnattendedUpdate
- authFlags.isPrimaryAuthRequiredAfterTimeout ->
- DeviceEntryRestrictionReason.SecurityTimeout
- authFlags.isPrimaryAuthRequiredAfterLockout ->
- DeviceEntryRestrictionReason.BouncerLockedOut
- isFingerprintLockedOut ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut
- isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
- authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
- DeviceEntryRestrictionReason.AdaptiveAuthRequest
- (trustEnabled && !trustManaged) &&
- (authFlags.someAuthRequiredAfterTrustAgentExpired ||
- authFlags.someAuthRequiredAfterUserRequest) ->
- DeviceEntryRestrictionReason.TrustAgentDisabled
- authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
- else -> null
- }
- }
- } else {
- flowOf(null)
- }
- }
-
- /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
- val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
-
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -268,27 +185,6 @@
return repository.isLockscreenEnabled()
}
- fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
- return when (this) {
- DeviceEntryRestrictionReason.UserLockdown -> true
- DeviceEntryRestrictionReason.PolicyLockdown -> true
-
- // Add individual enum value instead of using "else" so new reasons are guaranteed
- // to be added here at compile-time.
- null -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
- DeviceEntryRestrictionReason.BouncerLockedOut -> false
- DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
- DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
- DeviceEntryRestrictionReason.TrustAgentDisabled -> false
- DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
- DeviceEntryRestrictionReason.SecurityTimeout -> false
- DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
- DeviceEntryRestrictionReason.UnattendedUpdate -> false
- DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
- }
- }
-
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
@@ -296,12 +192,4 @@
* lockscreen.
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
- private val wasRebootedForMainlineUpdate
- get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
-
- companion object {
- @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
- @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5141690..e17e530 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,20 +16,26 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -49,6 +55,8 @@
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
private val deviceUnlockSource =
@@ -69,6 +77,75 @@
.map { DeviceUnlockSource.BouncerInput }
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.isLockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ) { authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate() ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ biometricSettingsInteractor.authenticationFlags.map { authFlags ->
+ when {
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ else -> null
+ }
+ }
+ }
+ }
+
+ /** Whether the device is in lockdown mode, where bouncer input is required to unlock. */
+ val isInLockdown: Flow<Boolean> = deviceEntryRestrictionReason.map { it.isInLockdown() }
+
/**
* Whether the device is unlocked or not, along with the information about the authentication
* method that was used to unlock the device.
@@ -90,13 +167,18 @@
// Device is locked if SIM is locked.
flowOf(DeviceUnlockStatus(false, null))
} else {
- powerInteractor.isAsleep.flatMapLatest { isAsleep ->
- if (isAsleep) {
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ combine(
+ powerInteractor.isAsleep,
+ isInLockdown,
+ ::Pair,
+ )
+ .flatMapLatestConflated { (isAsleep, isInLockdown) ->
+ if (isAsleep || isInLockdown) {
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ }
}
- }
}
}
.stateIn(
@@ -104,4 +186,34 @@
started = SharingStarted.Eagerly,
initialValue = DeviceUnlockStatus(false, null),
)
+
+ private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
+ return when (this) {
+ DeviceEntryRestrictionReason.UserLockdown -> true
+ DeviceEntryRestrictionReason.PolicyLockdown -> true
+
+ // Add individual enum value instead of using "else" so new reasons are guaranteed
+ // to be added here at compile-time.
+ null -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot -> false
+ DeviceEntryRestrictionReason.BouncerLockedOut -> false
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest -> false
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout -> false
+ DeviceEntryRestrictionReason.TrustAgentDisabled -> false
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut -> false
+ DeviceEntryRestrictionReason.SecurityTimeout -> false
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate -> false
+ DeviceEntryRestrictionReason.UnattendedUpdate -> false
+ DeviceEntryRestrictionReason.NonStrongFaceLockedOut -> false
+ }
+ }
+
+ private fun wasRebootedForMainlineUpdate(): Boolean {
+ return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+ }
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index d12ea45..c536d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -90,6 +90,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
+ deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -276,9 +277,13 @@
}
private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
+
/** Provide the status of face authentication */
override val authenticationStatus =
- merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+ merge(
+ faceAuthenticationStatusOverride.filterNotNull(),
+ deviceEntryFaceAuthStatusInteractor.authenticationStatus.filterNotNull(),
+ )
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index bbc3d76..89c7178 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -168,8 +168,7 @@
*/
@Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
suspend fun maybeHandleInsecurePowerGesture(): Boolean {
- // TODO(b/336576536): Check if adaptation for scene framework is needed
- if (SceneContainerFlag.isEnabled) return true
+ if (SceneContainerFlag.isEnabled) return false
if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
if (keyguardInteractor.isKeyguardDismissible.value) {
startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index 853f176..1fd609d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -26,6 +26,7 @@
import android.view.WindowManager
import android.widget.ProgressBar
import androidx.core.view.isGone
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import javax.inject.Inject
@@ -37,7 +38,7 @@
@Inject
constructor(
private val layoutInflater: LayoutInflater,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
) {
private var overlayView: View? = null
@@ -90,7 +91,7 @@
) {
if (overlayView == null) {
overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
- windowManager.addView(overlayView, overlayViewParams)
+ windowManager.addView(requireNotNull(overlayView), overlayViewParams)
progressBar?.pivotX = 0.0f
progressBar?.pivotY = 0.0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 18358a7..d8c13b6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -33,6 +33,7 @@
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
@@ -83,7 +84,7 @@
class BackPanelController
internal constructor(
context: Context,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
private val mainHandler: Handler,
private val systemClock: SystemClock,
@@ -102,7 +103,7 @@
class Factory
@Inject
constructor(
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val viewConfiguration: ViewConfiguration,
@BackPanelUiThread private val uiThreadContext: UiThreadContext,
private val systemClock: SystemClock,
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 c9be993..947336d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -91,6 +91,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.util.concurrency.BackPanelUiThread;
import com.android.systemui.util.concurrency.UiThreadContext;
@@ -297,6 +298,7 @@
private Date mTmpLogDate = new Date();
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
new NavigationEdgeBackPlugin.BackCallback() {
@@ -423,7 +425,8 @@
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController) {
mContext = context;
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
@@ -479,6 +482,7 @@
this::onNavigationSettingsChanged);
updateCurrentUserResources();
+ mNotificationShadeWindowController = notificationShadeWindowController;
}
public void setStateChangeCallback(Runnable callback) {
@@ -1297,6 +1301,9 @@
mBackAnimation.setPilferPointerCallback(() -> {
pilferPointers();
});
+ mBackAnimation.setTopUiRequestCallback(
+ (requestTopUi, tag) -> mUiThreadContext.getExecutor().execute(() ->
+ mNotificationShadeWindowController.setRequestTopUi(requestTopUi, tag)));
updateBackAnimationThresholds();
if (mLightBarControllerProvider.get() != null) {
mBackAnimation.setStatusBarCustomizer((appearance) -> {
@@ -1333,6 +1340,7 @@
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
private final Provider<LightBarController> mLightBarControllerProvider;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
@Inject
public Factory(OverviewProxyService overviewProxyService,
@@ -1353,7 +1361,8 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -1372,6 +1381,7 @@
mFalsingManager = falsingManager;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
+ mNotificationShadeWindowController = notificationShadeWindowController;
}
/** Construct a {@link EdgeBackGestureHandler}. */
@@ -1396,7 +1406,8 @@
mDesktopModeOptional,
mFalsingManager,
mBackGestureTfClassifierProviderProvider,
- mLightBarControllerProvider));
+ mLightBarControllerProvider,
+ mNotificationShadeWindowController));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index 0f36097..1dbd500 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -19,7 +19,6 @@
import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
@@ -38,7 +37,6 @@
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
-import android.media.permission.SafeCloseable;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.AttributeSet;
@@ -61,19 +59,18 @@
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.ScreenPinningNotify;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.views.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.views.buttons.ContextualButton;
import com.android.systemui.navigationbar.views.buttons.ContextualButtonGroup;
import com.android.systemui.navigationbar.views.buttons.DeadZone;
import com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.views.buttons.NearestTouchFrame;
-import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
@@ -181,7 +178,6 @@
private boolean mOverviewProxyEnabled;
private boolean mShowSwipeUpUi;
private UpdateActiveTouchRegionsCallback mUpdateActiveTouchRegionsCallback;
- private SafeCloseable mViewCaptureCloseable;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -1082,10 +1078,6 @@
}
updateNavButtonIcons();
- if (enableViewCaptureTracing()) {
- mViewCaptureCloseable = ViewCaptureFactory.getInstance(getContext())
- .startCapture(getRootView(), ".NavigationBarView");
- }
}
@Override
@@ -1098,9 +1090,6 @@
mFloatingRotationButton.hide();
mRotationButtonController.unregisterListeners();
}
- if (mViewCaptureCloseable != null) {
- mViewCaptureCloseable.close();
- }
}
void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index df7430a..158eb6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -65,6 +65,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -184,7 +185,7 @@
private GlobalSettings mGlobalSettings;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
@@ -246,7 +247,7 @@
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
- WindowManager windowManager, ToastFactory toastFactory,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, ToastFactory toastFactory,
@Background Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
@@ -278,7 +279,7 @@
mAccessPointController = accessPointController;
mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
mConnectivityManagerNetworkCallback = new DataConnectivityListener();
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
mSecondarySignalDrawable = new SignalDrawable(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1f4820a..8711e88 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -665,7 +665,7 @@
keyguardEnabledInteractor.isKeyguardEnabled
.sample(
combine(
- deviceEntryInteractor.isInLockdown,
+ deviceUnlockedInteractor.isInLockdown,
deviceEntryInteractor.isDeviceEntered,
::Pair,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index abf258c..693cc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -485,9 +485,8 @@
final boolean intersectsTopCutout = topDisplayCutout.intersects(
width - (windowWidth / 2), 0,
width + (windowWidth / 2), topDisplayCutout.bottom);
- if (mClingWindow != null &&
- (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
- final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+ final View iconView = findViewById(R.id.immersive_cling_icon);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
iconView.getLayoutParams();
lp.topMargin = topDisplayCutout.bottom;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 1789ad6..2081adc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3519,7 +3519,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startOverscrollAfterExpanding() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
getExpandHelper().finishExpanding();
setIsBeingDragged(true);
}
@@ -3527,7 +3527,7 @@
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
void startDraggingOnHun() {
- SceneContainerFlag.isUnexpectedlyInLegacyMode();
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
setIsBeingDragged(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 726fdee..a072ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2034,6 +2034,7 @@
hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev);
if (hunWantsIt) {
mView.startDraggingOnHun();
+ mHeadsUpManager.unpinAll(true);
}
}
boolean swipeWantsIt = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index eb1f778..1a7bc16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -110,6 +110,11 @@
interactor.setScrolledToTop(scrolledToTop)
}
+ /** Sets whether the heads up notification is animating away. */
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
+
/** Snooze the currently pinned HUN. */
fun snoozeHun() {
headsUpNotificationInteractor.snooze()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 7f16e18..26bd7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -124,7 +124,6 @@
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
mPanel.startExpand(x, y, true /* startTracking */, startHeight);
- // TODO(b/340514839): Figure out where to move this side effect in flexiglass
if (!SceneContainerFlag.isEnabled()) {
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 43ab337..40799583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -91,7 +91,11 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ if (runnable == null) {
+ log { "Runnable is NULL, stop update." }
+ return
+ }
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
@@ -147,7 +151,11 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ if (runnable == null) {
+ log { "Runnable is NULL, stop delete." }
+ return
+ }
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index de8b9b1..eb2f71a1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -73,10 +73,19 @@
@Provides
@SysUISingleton
fun provideAudioSharingRepository(
+ @Application context: Context,
+ contentResolver: ContentResolver,
localBluetoothManager: LocalBluetoothManager?,
+ @Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- AudioSharingRepositoryImpl(localBluetoothManager, coroutineContext)
+ AudioSharingRepositoryImpl(
+ context,
+ contentResolver,
+ localBluetoothManager,
+ coroutineScope,
+ coroutineContext
+ )
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
new file mode 100644
index 0000000..2904092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for empty audio sharing impl for unnecessary volume overlay */
+@Module
+interface AudioSharingEmptyImplModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(): AudioSharingInteractor =
+ AudioSharingInteractorEmptyImpl()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
new file mode 100644
index 0000000..9f1e60e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio sharing code in the volume package */
+@Module
+interface AudioSharingModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(
+ @Application coroutineScope: CoroutineScope,
+ repository: AudioSharingRepository
+ ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 5420988..ebb9ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -59,6 +59,7 @@
@Module(
includes = {
AudioModule.class,
+ AudioSharingModule.class,
AncModule.class,
CaptioningModule.class,
MediaDevicesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
new file mode 100644
index 0000000..4d29788
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.interactor
+
+import android.bluetooth.BluetoothCsipSetCoordinator
+import androidx.annotation.IntRange
+import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.launch
+
+interface AudioSharingInteractor {
+ /** Audio sharing secondary headset volume changes. */
+ val volume: Flow<Int?>
+
+ /** Audio sharing secondary headset min volume. */
+ val volumeMin: Int
+
+ /** Audio sharing secondary headset max volume. */
+ val volumeMax: Int
+
+ /** Set the volume of the secondary headset in audio sharing. */
+ fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ )
+}
+
+@SysUISingleton
+class AudioSharingInteractorImpl
+@Inject
+constructor(
+ @Application private val coroutineScope: CoroutineScope,
+ private val audioSharingRepository: AudioSharingRepository
+) : AudioSharingInteractor {
+
+ override val volume: Flow<Int?> =
+ combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
+ secondaryGroupId,
+ volumeMap ->
+ if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
+ else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
+ }
+
+ override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
+
+ override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
+
+ override fun setStreamVolume(level: Int) {
+ coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
+ }
+
+ private companion object {
+ const val DEFAULT_VOLUME = 20
+ }
+}
+
+@SysUISingleton
+class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
+ override val volume: Flow<Int?> = emptyFlow()
+ override val volumeMin: Int = EMPTY_VOLUME
+ override val volumeMax: Int = EMPTY_VOLUME
+
+ override fun setStreamVolume(level: Int) {}
+
+ private companion object {
+ const val EMPTY_VOLUME = 0
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
new file mode 100644
index 0000000..baef620
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDebouncerTest.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.biometrics
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class FaceHelpMessageDebouncerTest : SysuiTestCase() {
+ private lateinit var underTest: FaceHelpMessageDebouncer
+ private val window = 9L
+ private val startWindow = 4L
+ private val shownFaceMessageFrequencyBoost = 2
+
+ @Before
+ fun setUp() {
+ underTest =
+ FaceHelpMessageDebouncer(
+ window = window,
+ startWindow = startWindow,
+ shownFaceMessageFrequencyBoost = shownFaceMessageFrequencyBoost,
+ )
+ }
+
+ @Test
+ fun getMessageBeforeStartWindow_null() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "testTooClose",
+ 0
+ )
+ )
+ assertThat(underTest.getMessageToShow(0)).isNull()
+ }
+
+ @Test
+ fun getMessageAfterStartWindow() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+
+ assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+ assertThat(underTest.getMessageToShow(startWindow)?.msg).isEqualTo("tooClose")
+ }
+
+ @Test
+ fun getMessageAfterMessagesCleared_null() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.startNewFaceAuthSession(0)
+
+ assertThat(underTest.getMessageToShow(startWindow)).isNull()
+ }
+
+ @Test
+ fun messagesBeforeWindowRemoved() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ 0
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ window - 1
+ )
+ )
+ val lastMessage =
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ window
+ )
+ underTest.addMessage(lastMessage)
+
+ assertThat(underTest.getMessageToShow(window + 1)).isEqualTo(lastMessage)
+ }
+
+ @Test
+ fun getMessageTieGoesToMostRecent() {
+ for (i in 1..window step 2) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ i
+ )
+ )
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ i + 1
+ )
+ )
+ }
+
+ assertThat(underTest.getMessageToShow(window)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+ assertThat(underTest.getMessageToShow(window)?.msg).isEqualTo("tooBright")
+ }
+
+ @Test
+ fun boostCurrentlyShowingMessage() {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ 0
+ )
+ )
+
+ val lastMessageShown = underTest.getMessageToShow(startWindow)
+ assertThat(lastMessageShown?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT)
+
+ for (i in 1..<shownFaceMessageFrequencyBoost) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+ }
+
+ // although technically there's a different msgId with a higher frequency count now, the
+ // shownFaceMessageFrequencyBoost causes the last message shown to get a "boost"
+ // to keep showing
+ assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(lastMessageShown)
+ }
+
+ @Test
+ fun overcomeBoostedCurrentlyShowingMessage() {
+ // Comments are assuming shownFaceMessageFrequencyBoost = 2
+ // [B], weights: B=1
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT,
+ "tooBright",
+ 0
+ )
+ )
+
+ // [B], showing messageB, weights: B=3
+ val messageB = underTest.getMessageToShow(startWindow)
+
+ // [B, C, C], showing messageB, weights: B=3, C=2
+ for (i in 1..shownFaceMessageFrequencyBoost) {
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+ }
+ // messageB is getting boosted to continue to show
+ assertThat(underTest.getMessageToShow(startWindow)).isEqualTo(messageB)
+
+ // receive one more FACE_ACQUIRED_TOO_CLOSE acquired info to pass the boost
+ // [C, C, C], showing messageB, weights: B=2, C=3
+ underTest.addMessage(
+ HelpFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE,
+ "tooClose",
+ startWindow
+ )
+ )
+
+ // Now FACE_ACQUIRED_TOO_CLOSE has surpassed the boosted messageB frequency
+ // [C, C, C], showing messageC, weights: C=5
+ assertThat(underTest.getMessageToShow(startWindow)?.msgId)
+ .isEqualTo(BiometricFaceConstants.FACE_ACQUIRED_TOO_CLOSE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
index 431fef6..6fd8660 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
@@ -24,6 +24,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.domain.faceHelpMessageDeferral
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -45,6 +46,7 @@
import com.android.systemui.util.mockito.whenever
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
@@ -264,11 +266,20 @@
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- // WHEN authentication status help
+ // WHEN authentication status help past debouncer
faceAuthRepository.setAuthenticationStatus(
HelpFaceAuthenticationStatus(
msg = "Move left",
msgId = FACE_ACQUIRED_TOO_RIGHT,
+ createdAt = 0L,
+ )
+ )
+ runCurrent()
+ faceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ msg = "Move left",
+ msgId = FACE_ACQUIRED_TOO_RIGHT,
+ createdAt = FaceHelpMessageDebouncer.DEFAULT_WINDOW_MS,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 529cd6e..0b7a3ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -90,6 +90,7 @@
private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
private val trustManager = kosmos.trustManager
+ private val deviceEntryFaceAuthStatusInteractor = kosmos.deviceEntryFaceAuthStatusInteractor
@Before
fun setup() {
@@ -112,6 +113,7 @@
powerInteractor,
fakeBiometricSettingsRepository,
trustManager,
+ deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
new file mode 100644
index 0000000..6022d9c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.face.FaceManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FaceHelpMessageDebouncer.Companion.DEFAULT_WINDOW_MS
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFaceAuthStatusInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope: TestScope = kosmos.testScope
+ private lateinit var underTest: DeviceEntryFaceAuthStatusInteractor
+ private val ignoreHelpMessageId = 1
+
+ @Before
+ fun setup() {
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(ignoreHelpMessageId)
+ )
+ underTest = kosmos.deviceEntryFaceAuthStatusInteractor
+ }
+
+ @Test
+ fun successAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val successStatus =
+ SuccessFaceAuthenticationStatus(
+ successResult = mock(FaceManager.AuthenticationResult::class.java)
+ )
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(successStatus)
+ assertThat(authenticationStatus).isEqualTo(successStatus)
+ }
+
+ @Test
+ fun acquiredFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val acquiredStatus = AcquiredFaceAuthenticationStatus(acquiredInfo = 0)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(acquiredStatus)
+ assertThat(authenticationStatus).isEqualTo(acquiredStatus)
+ }
+
+ @Test
+ fun failedFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val failedStatus = FailedFaceAuthenticationStatus()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(failedStatus)
+ assertThat(authenticationStatus).isEqualTo(failedStatus)
+ }
+
+ @Test
+ fun errorFaceAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val errorStatus = ErrorFaceAuthenticationStatus(0, "test")
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(errorStatus)
+ assertThat(authenticationStatus).isEqualTo(errorStatus)
+ }
+
+ @Test
+ fun firstHelpFaceAuthenticationStatus_noUpdate() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ AcquiredFaceAuthenticationStatus(
+ BiometricFaceConstants.FACE_ACQUIRED_START,
+ createdAt = 0
+ )
+ )
+ val helpMessage = HelpFaceAuthenticationStatus(0, "test", 1)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+ assertThat(authenticationStatus).isNull()
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_afterWindow() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(0, "test1", 0)
+ )
+ runCurrent()
+ val helpMessage = HelpFaceAuthenticationStatus(0, "test2", DEFAULT_WINDOW_MS)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(helpMessage)
+ runCurrent()
+ assertThat(authenticationStatus).isEqualTo(helpMessage)
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_onlyIgnoredHelpMessages_afterWindow() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", 0)
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+ )
+ runCurrent()
+ assertThat(authenticationStatus).isNull()
+ }
+
+ @Test
+ fun helpFaceAuthenticationStatus_afterWindow_onIgnoredMessage_showsOtherMessageInstead() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ val validHelpMessage = HelpFaceAuthenticationStatus(0, "validHelpMsg", 0)
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(validHelpMessage)
+ runCurrent()
+ // help message that should be ignored
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(ignoreHelpMessageId, "ignoredMsg", DEFAULT_WINDOW_MS)
+ )
+ runCurrent()
+ assertThat(authenticationStatus).isEqualTo(validHelpMessage)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index b169cc1..b4db6da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -27,6 +27,7 @@
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
@@ -63,7 +64,7 @@
private var triggerThreshold: Float = 0.0f
private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
@Mock private lateinit var vibratorHelper: VibratorHelper
- @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
private val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
@@ -78,7 +79,7 @@
mBackPanelController =
BackPanelController(
context,
- windowManager,
+ viewCaptureAwareWindowManager,
ViewConfiguration.get(context),
Handler.createAsync(testableLooper.looper),
systemClock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 5273495..eea02ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -61,6 +61,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.wifi.WifiUtils;
@@ -160,7 +161,7 @@
@Mock
InternetDialogController.InternetDialogCallback mInternetDialogCallback;
@Mock
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
@Mock
private ToastFactory mToastFactory;
@Mock
@@ -232,8 +233,9 @@
mSubscriptionManager, mTelephonyManager, mWifiManager,
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
- mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController, mDialogTransitionAnimator, mWifiStateWorker, mFlags);
+ mWindowManager, mToastFactory, mWorkerHandler,
+ mCarrierConfigTracker, mLocationController, mDialogTransitionAnimator,
+ mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index 4b64416..de5f0f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
@@ -44,7 +44,7 @@
clock = systemClock,
biometricMessageInteractor = biometricMessageInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintInteractor = deviceEntryFingerprintAuthInteractor,
flags = composeBouncerFlags,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 25e7729..045bd5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -30,16 +30,10 @@
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
- var userPresentCount = 0
-
override suspend fun isLockscreenEnabled(): Boolean {
return isLockscreenEnabled
}
- override suspend fun reportUserPresent() {
- userPresentCount++
- }
-
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
this.isLockscreenEnabled = isLockscreenEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index a8fc27a..b9be04d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -57,5 +57,6 @@
powerInteractor = powerInteractor,
biometricSettingsRepository = biometricSettingsRepository,
trustManager = trustManager,
+ deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
new file mode 100644
index 0000000..66d3709
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import android.content.res.mainResources
+import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.deviceEntryFaceAuthStatusInteractor by
+ Kosmos.Fixture {
+ DeviceEntryFaceAuthStatusInteractor(
+ repository = deviceEntryFaceAuthRepository,
+ resources = mainResources,
+ applicationScope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 1200866..caa6e99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -19,8 +19,6 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,12 +32,7 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
- trustInteractor = trustInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
- systemPropertiesHelper = fakeSystemPropertiesHelper,
alternateBouncerInteractor = alternateBouncerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 14210bc..1ed10fbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.flags.systemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -33,5 +35,7 @@
faceAuthInteractor = deviceEntryFaceAuthInteractor,
fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
powerInteractor = powerInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index 327e1b5..d391750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,16 +16,40 @@
package com.android.systemui.volume.data.repository
+import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
+import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.data.repository.GroupIdToVolumes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
class FakeAudioSharingRepository : AudioSharingRepository {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val mutableSecondaryGroupId: MutableStateFlow<Int> =
+ MutableStateFlow(TEST_GROUP_ID_INVALID)
+ private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+ override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
+ override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
+
+ override suspend fun setSecondaryVolume(volume: Int) {}
fun setInAudioSharing(state: Boolean) {
mutableInAudioSharing.value = state
}
+
+ fun setSecondaryGroupId(groupId: Int) {
+ mutableSecondaryGroupId.value = groupId
+ }
+
+ fun setVolumeMap(volumeMap: GroupIdToVolumes) {
+ mutableVolumeMap.value = volumeMap
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
new file mode 100644
index 0000000..03981bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.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.volume.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.data.repository.audioSharingRepository
+
+val Kosmos.audioSharingInteractor by
+ Kosmos.Fixture {
+ AudioSharingInteractorImpl(
+ applicationCoroutineScope,
+ audioSharingRepository,
+ )
+ }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index e031eb2..8a1fe62 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -30,7 +30,6 @@
return RavenwoodRuntimeNative.lseek(fd, offset, whence);
}
-
public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
return RavenwoodRuntimeNative.pipe2(flags);
}
@@ -42,4 +41,16 @@
public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
return RavenwoodRuntimeNative.fcntlInt(fd, cmd, arg);
}
+
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+ return RavenwoodRuntimeNative.fstat(fd);
+ }
+
+ public static StructStat lstat(String path) throws ErrnoException {
+ return RavenwoodRuntimeNative.lstat(path);
+ }
+
+ public static StructStat stat(String path) throws ErrnoException {
+ return RavenwoodRuntimeNative.stat(path);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
new file mode 100644
index 0000000..a8b1fca
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructStat.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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.system;
+
+import libcore.util.Objects;
+
+/**
+ * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}.
+ * Corresponds to C's {@code struct stat} from {@code <stat.h>}.
+ */
+public final class StructStat {
+ /** Device ID of device containing file. */
+ public final long st_dev; /*dev_t*/
+
+ /** File serial number (inode). */
+ public final long st_ino; /*ino_t*/
+
+ /** Mode (permissions) of file. */
+ public final int st_mode; /*mode_t*/
+
+ /** Number of hard links to the file. */
+ public final long st_nlink; /*nlink_t*/
+
+ /** User ID of file. */
+ public final int st_uid; /*uid_t*/
+
+ /** Group ID of file. */
+ public final int st_gid; /*gid_t*/
+
+ /** Device ID (if file is character or block special). */
+ public final long st_rdev; /*dev_t*/
+
+ /**
+ * For regular files, the file size in bytes.
+ * For symbolic links, the length in bytes of the pathname contained in the symbolic link.
+ * For a shared memory object, the length in bytes.
+ * For a typed memory object, the length in bytes.
+ * For other file types, the use of this field is unspecified.
+ */
+ public final long st_size; /*off_t*/
+
+ /** Seconds part of time of last access. */
+ public final long st_atime; /*time_t*/
+
+ /** StructTimespec with time of last access. */
+ public final StructTimespec st_atim;
+
+ /** Seconds part of time of last data modification. */
+ public final long st_mtime; /*time_t*/
+
+ /** StructTimespec with time of last modification. */
+ public final StructTimespec st_mtim;
+
+ /** Seconds part of time of last status change */
+ public final long st_ctime; /*time_t*/
+
+ /** StructTimespec with time of last status change. */
+ public final StructTimespec st_ctim;
+
+ /**
+ * A file system-specific preferred I/O block size for this object.
+ * For some file system types, this may vary from file to file.
+ */
+ public final long st_blksize; /*blksize_t*/
+
+ /** Number of blocks allocated for this object. */
+ public final long st_blocks; /*blkcnt_t*/
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime,
+ long st_blksize, long st_blocks) {
+ this(st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid,
+ st_rdev, st_size, new StructTimespec(st_atime, 0L), new StructTimespec(st_mtime, 0L),
+ new StructTimespec(st_ctime, 0L), st_blksize, st_blocks);
+ }
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid,
+ long st_rdev, long st_size, StructTimespec st_atim, StructTimespec st_mtim,
+ StructTimespec st_ctim, long st_blksize, long st_blocks) {
+ this.st_dev = st_dev;
+ this.st_ino = st_ino;
+ this.st_mode = st_mode;
+ this.st_nlink = st_nlink;
+ this.st_uid = st_uid;
+ this.st_gid = st_gid;
+ this.st_rdev = st_rdev;
+ this.st_size = st_size;
+ this.st_atime = st_atim.tv_sec;
+ this.st_mtime = st_mtim.tv_sec;
+ this.st_ctime = st_ctim.tv_sec;
+ this.st_atim = st_atim;
+ this.st_mtim = st_mtim;
+ this.st_ctim = st_ctim;
+ this.st_blksize = st_blksize;
+ this.st_blocks = st_blocks;
+ }
+
+ @Override public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
new file mode 100644
index 0000000..c106780
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/StructTimespec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import libcore.util.Objects;;
+
+/**
+ * Corresponds to C's {@code struct timespec} from {@code <time.h>}.
+ */
+public final class StructTimespec implements Comparable<StructTimespec> {
+ /** Seconds part of time of last data modification. */
+ public final long tv_sec; /*time_t*/
+
+ /** Nanoseconds (values are [0, 999999999]). */
+ public final long tv_nsec;
+
+ public StructTimespec(long tv_sec, long tv_nsec) {
+ this.tv_sec = tv_sec;
+ this.tv_nsec = tv_nsec;
+ if (tv_nsec < 0 || tv_nsec > 999_999_999) {
+ throw new IllegalArgumentException(
+ "tv_nsec value " + tv_nsec + " is not in [0, 999999999]");
+ }
+ }
+
+ @Override
+ public int compareTo(StructTimespec other) {
+ if (tv_sec > other.tv_sec) {
+ return 1;
+ }
+ if (tv_sec < other.tv_sec) {
+ return -1;
+ }
+ if (tv_nsec > other.tv_nsec) {
+ return 1;
+ }
+ if (tv_nsec < other.tv_nsec) {
+ return -1;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StructTimespec that = (StructTimespec) o;
+
+ if (tv_sec != that.tv_sec) return false;
+ return tv_nsec == that.tv_nsec;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (tv_sec ^ (tv_sec >>> 32));
+ result = 31 * result + (int) (tv_nsec ^ (tv_nsec >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(this);
+ }
+}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
similarity index 65%
rename from ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
rename to ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index 6540221..e9b305e 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -15,6 +15,9 @@
*/
package com.android.ravenwood.common;
+import android.system.ErrnoException;
+import android.system.StructStat;
+
import java.io.FileDescriptor;
/**
@@ -31,19 +34,25 @@
public static native void applyFreeFunction(long freeFunction, long nativePtr);
- public static native long nLseek(int fd, long offset, int whence);
+ private static native long nLseek(int fd, long offset, int whence) throws ErrnoException;
- public static native int[] nPipe2(int flags);
+ private static native int[] nPipe2(int flags) throws ErrnoException;
- public static native int nDup(int oldfd);
+ private static native int nDup(int oldfd) throws ErrnoException;
- public static native int nFcntlInt(int fd, int cmd, int arg);
+ private static native int nFcntlInt(int fd, int cmd, int arg) throws ErrnoException;
- public static long lseek(FileDescriptor fd, long offset, int whence) {
+ private static native StructStat nFstat(int fd) throws ErrnoException;
+
+ public static native StructStat lstat(String path) throws ErrnoException;
+
+ public static native StructStat stat(String path) throws ErrnoException;
+
+ public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
- public static FileDescriptor[] pipe2(int flags) {
+ public static FileDescriptor[] pipe2(int flags) throws ErrnoException {
var fds = nPipe2(flags);
var ret = new FileDescriptor[] {
new FileDescriptor(),
@@ -55,7 +64,7 @@
return ret;
}
- public static FileDescriptor dup(FileDescriptor fd) {
+ public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException {
var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd));
var retFd = new java.io.FileDescriptor();
@@ -63,9 +72,15 @@
return retFd;
}
- public static int fcntlInt(FileDescriptor fd, int cmd, int arg) {
+ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException {
var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
return nFcntlInt(fdInt, cmd, arg);
}
+
+ public static StructStat fstat(FileDescriptor fd) throws ErrnoException {
+ var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
+
+ return nFstat(fdInt);
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
new file mode 100644
index 0000000..3781fcf
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Objects.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 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 libcore.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public final class Objects {
+ private Objects() {}
+
+ /**
+ * Returns a string reporting the value of each declared field, via reflection.
+ * Static and transient fields are automatically skipped. Produces output like
+ * "SimpleClassName[integer=1234,string="hello",character='c',intArray=[1,2,3]]".
+ */
+ public static String toString(Object o) {
+ Class<?> c = o.getClass();
+ StringBuilder sb = new StringBuilder();
+ sb.append(c.getSimpleName()).append('[');
+ int i = 0;
+ for (Field f : c.getDeclaredFields()) {
+ if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) {
+ continue;
+ }
+ f.setAccessible(true);
+ try {
+ Object value = f.get(o);
+
+ if (i++ > 0) {
+ sb.append(',');
+ }
+
+ sb.append(f.getName());
+ sb.append('=');
+
+ if (value.getClass().isArray()) {
+ if (value.getClass() == boolean[].class) {
+ sb.append(Arrays.toString((boolean[]) value));
+ } else if (value.getClass() == byte[].class) {
+ sb.append(Arrays.toString((byte[]) value));
+ } else if (value.getClass() == char[].class) {
+ sb.append(Arrays.toString((char[]) value));
+ } else if (value.getClass() == double[].class) {
+ sb.append(Arrays.toString((double[]) value));
+ } else if (value.getClass() == float[].class) {
+ sb.append(Arrays.toString((float[]) value));
+ } else if (value.getClass() == int[].class) {
+ sb.append(Arrays.toString((int[]) value));
+ } else if (value.getClass() == long[].class) {
+ sb.append(Arrays.toString((long[]) value));
+ } else if (value.getClass() == short[].class) {
+ sb.append(Arrays.toString((short[]) value));
+ } else {
+ sb.append(Arrays.toString((Object[]) value));
+ }
+ } else if (value.getClass() == Character.class) {
+ sb.append('\'').append(value).append('\'');
+ } else if (value.getClass() == String.class) {
+ sb.append('"').append(value).append('"');
+ } else {
+ sb.append(value);
+ }
+ } catch (IllegalAccessException unexpected) {
+ throw new AssertionError(unexpected);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 34cf9f9..e0a3e1c 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -19,6 +19,9 @@
#include <string.h>
#include <unistd.h>
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
@@ -41,6 +44,75 @@
return rc;
}
+// ---- Helper functions ---
+
+static jclass g_StructStat;
+static jclass g_StructTimespecClass;
+
+static jclass findClass(JNIEnv* env, const char* name) {
+ ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
+ jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
+ if (result == NULL) {
+ ALOGE("failed to find class '%s'", name);
+ abort();
+ }
+ return result;
+}
+
+static jobject makeStructTimespec(JNIEnv* env, const struct timespec& ts) {
+ static jmethodID ctor = env->GetMethodID(g_StructTimespecClass, "<init>",
+ "(JJ)V");
+ if (ctor == NULL) {
+ return NULL;
+ }
+ return env->NewObject(g_StructTimespecClass, ctor,
+ static_cast<jlong>(ts.tv_sec), static_cast<jlong>(ts.tv_nsec));
+}
+
+static jobject makeStructStat(JNIEnv* env, const struct stat64& sb) {
+ static jmethodID ctor = env->GetMethodID(g_StructStat, "<init>",
+ "(JJIJIIJJLandroid/system/StructTimespec;Landroid/system/StructTimespec;Landroid/system/StructTimespec;JJ)V");
+ if (ctor == NULL) {
+ return NULL;
+ }
+
+ jobject atim_timespec = makeStructTimespec(env, sb.st_atim);
+ if (atim_timespec == NULL) {
+ return NULL;
+ }
+ jobject mtim_timespec = makeStructTimespec(env, sb.st_mtim);
+ if (mtim_timespec == NULL) {
+ return NULL;
+ }
+ jobject ctim_timespec = makeStructTimespec(env, sb.st_ctim);
+ if (ctim_timespec == NULL) {
+ return NULL;
+ }
+
+ return env->NewObject(g_StructStat, ctor,
+ static_cast<jlong>(sb.st_dev), static_cast<jlong>(sb.st_ino),
+ static_cast<jint>(sb.st_mode), static_cast<jlong>(sb.st_nlink),
+ static_cast<jint>(sb.st_uid), static_cast<jint>(sb.st_gid),
+ static_cast<jlong>(sb.st_rdev), static_cast<jlong>(sb.st_size),
+ atim_timespec, mtim_timespec, ctim_timespec,
+ static_cast<jlong>(sb.st_blksize), static_cast<jlong>(sb.st_blocks));
+}
+
+static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
+ ScopedUtfChars path(env, javaPath);
+ if (path.c_str() == NULL) {
+ return NULL;
+ }
+ struct stat64 sb;
+ int rc = isLstat ? TEMP_FAILURE_RETRY(lstat64(path.c_str(), &sb))
+ : TEMP_FAILURE_RETRY(stat64(path.c_str(), &sb));
+ if (rc == -1) {
+ throwErrnoException(env, isLstat ? "lstat" : "stat");
+ return NULL;
+ }
+ return makeStructStat(env, sb);
+}
+
// ---- JNI methods ----
typedef void (*FreeFunction)(void*);
@@ -77,6 +149,24 @@
return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, F_DUPFD_CLOEXEC, 0)));
}
+static jobject nFstat(JNIEnv* env, jobject, jint fd) {
+ struct stat64 sb;
+ int rc = TEMP_FAILURE_RETRY(fstat64(fd, &sb));
+ if (rc == -1) {
+ throwErrnoException(env, "fstat");
+ return NULL;
+ }
+ return makeStructStat(env, sb);
+}
+
+static jobject Linux_lstat(JNIEnv* env, jobject, jstring javaPath) {
+ return doStat(env, javaPath, true);
+}
+
+static jobject Linux_stat(JNIEnv* env, jobject, jstring javaPath) {
+ return doStat(env, javaPath, false);
+}
+
// ---- Registration ----
static const JNINativeMethod sMethods[] =
@@ -86,6 +176,9 @@
{ "nLseek", "(IJI)J", (void*)nLseek },
{ "nPipe2", "(I)[I", (void*)nPipe2 },
{ "nDup", "(I)I", (void*)nDup },
+ { "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
+ { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
+ { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -101,6 +194,9 @@
ALOGI("%s: JNI_OnLoad", __FILE__);
+ g_StructStat = findClass(env, "android/system/StructStat");
+ g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
+
jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
sMethods, NELEM(sMethods));
if (res < 0) {
diff --git a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
index b5038e6..05275b2 100644
--- a/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
+++ b/ravenwood/runtime-test/test/com/android/ravenwood/runtimetest/OsTest.java
@@ -15,15 +15,26 @@
*/
package com.android.ravenwood.runtimetest;
+import static android.system.OsConstants.S_ISBLK;
+import static android.system.OsConstants.S_ISCHR;
+import static android.system.OsConstants.S_ISDIR;
+import static android.system.OsConstants.S_ISFIFO;
+import static android.system.OsConstants.S_ISLNK;
+import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
+
import static org.junit.Assert.assertEquals;
+import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
+
import android.system.Os;
import android.system.OsConstants;
+import android.system.StructStat;
+import android.system.StructTimespec;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,8 +43,15 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class OsTest {
@@ -41,7 +59,7 @@
void accept(T var1) throws Exception;
}
- private void withTestFile(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
+ private void withTestFileFD(ConsumerWithThrow<FileDescriptor> consumer) throws Exception {
File file = File.createTempFile("osTest", "bin");
try (var raf = new RandomAccessFile(file, "rw")) {
var fd = raf.getFD();
@@ -57,9 +75,20 @@
}
}
+ private void withTestFile(ConsumerWithThrow<Path> consumer) throws Exception {
+ var path = Files.createTempFile("osTest", "bin");
+ try (var os = Files.newOutputStream(path)) {
+ os.write(1);
+ os.write(2);
+ os.write(3);
+ os.write(4);
+ }
+ consumer.accept(path);
+ }
+
@Test
public void testLseek() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
assertEquals(4, Os.lseek(fd, 4, OsConstants.SEEK_SET));
assertEquals(4, Os.lseek(fd, 0, OsConstants.SEEK_CUR));
assertEquals(6, Os.lseek(fd, 2, OsConstants.SEEK_CUR));
@@ -68,7 +97,7 @@
@Test
public void testDup() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
var dup = Os.dup(fd);
checkAreDup(fd, dup);
@@ -85,7 +114,7 @@
@Test
public void testFcntlInt() throws Exception {
- withTestFile((fd) -> {
+ withTestFileFD((fd) -> {
var dupInt = Os.fcntlInt(fd, 0, 0);
var dup = new FileDescriptor();
@@ -95,16 +124,90 @@
});
}
- private static void write(FileDescriptor fd, int oneByte) throws IOException {
+ @Test
+ public void testStat() throws Exception {
+ withTestFile(path -> {
+ var attr = Files.readAttributes(path, PosixFileAttributes.class);
+ var stat = Os.stat(path.toAbsolutePath().toString());
+ assertAttributesEqual(attr, stat);
+ });
+ }
+
+ @Test
+ public void testLstat() throws Exception {
+ withTestFile(path -> {
+ // Create a symbolic link
+ var lnk = Files.createTempFile("osTest", "lnk");
+ Files.delete(lnk);
+ Files.createSymbolicLink(lnk, path);
+
+ // Test lstat
+ var attr = Files.readAttributes(lnk, PosixFileAttributes.class, NOFOLLOW_LINKS);
+ var stat = Os.lstat(lnk.toAbsolutePath().toString());
+ assertAttributesEqual(attr, stat);
+
+ // Test stat
+ var followAttr = Files.readAttributes(lnk, PosixFileAttributes.class);
+ var followStat = Os.stat(lnk.toAbsolutePath().toString());
+ assertAttributesEqual(followAttr, followStat);
+ });
+ }
+
+ @Test
+ public void testFstat() throws Exception {
+ withTestFile(path -> {
+ var attr = Files.readAttributes(path, PosixFileAttributes.class);
+ try (var raf = new RandomAccessFile(path.toFile(), "r")) {
+ var fd = raf.getFD();
+ var stat = Os.fstat(fd);
+ assertAttributesEqual(attr, stat);
+ }
+ });
+ }
+
+ // Verify StructStat values from libcore against native JVM PosixFileAttributes
+ private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
+ assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
+ assertEquals(attr.size(), stat.st_size);
+ assertEquals(attr.isDirectory(), S_ISDIR(stat.st_mode));
+ assertEquals(attr.isRegularFile(), S_ISREG(stat.st_mode));
+ assertEquals(attr.isSymbolicLink(), S_ISLNK(stat.st_mode));
+ assertEquals(attr.isOther(), S_ISCHR(stat.st_mode)
+ || S_ISBLK(stat.st_mode) || S_ISFIFO(stat.st_mode) || S_ISSOCK(stat.st_mode));
+ assertEquals(attr.permissions(), convertModeToPosixPerms(stat.st_mode));
+
+ }
+
+ private static FileTime convertTimespecToFileTime(StructTimespec ts) {
+ var nanos = TimeUnit.SECONDS.toNanos(ts.tv_sec);
+ nanos += ts.tv_nsec;
+ return FileTime.from(nanos, TimeUnit.NANOSECONDS);
+ }
+
+ private static Set<PosixFilePermission> convertModeToPosixPerms(int mode) {
+ var set = new HashSet<PosixFilePermission>();
+ if ((mode & OsConstants.S_IRUSR) != 0) set.add(PosixFilePermission.OWNER_READ);
+ if ((mode & OsConstants.S_IWUSR) != 0) set.add(PosixFilePermission.OWNER_WRITE);
+ if ((mode & OsConstants.S_IXUSR) != 0) set.add(PosixFilePermission.OWNER_EXECUTE);
+ if ((mode & OsConstants.S_IRGRP) != 0) set.add(PosixFilePermission.GROUP_READ);
+ if ((mode & OsConstants.S_IWGRP) != 0) set.add(PosixFilePermission.GROUP_WRITE);
+ if ((mode & OsConstants.S_IXGRP) != 0) set.add(PosixFilePermission.GROUP_EXECUTE);
+ if ((mode & OsConstants.S_IROTH) != 0) set.add(PosixFilePermission.OTHERS_READ);
+ if ((mode & OsConstants.S_IWOTH) != 0) set.add(PosixFilePermission.OTHERS_WRITE);
+ if ((mode & OsConstants.S_IXOTH) != 0) set.add(PosixFilePermission.OTHERS_EXECUTE);
+ return set;
+ }
+
+ private static void write(FileDescriptor fd, int oneByte) throws Exception {
// Create a dup to avoid closing the FD.
- try (var dup = new FileOutputStream(RavenwoodRuntimeNative.dup(fd))) {
+ try (var dup = new FileOutputStream(Os.dup(fd))) {
dup.write(oneByte);
}
}
- private static int read(FileDescriptor fd) throws IOException {
+ private static int read(FileDescriptor fd) throws Exception {
// Create a dup to avoid closing the FD.
- try (var dup = new FileInputStream(RavenwoodRuntimeNative.dup(fd))) {
+ try (var dup = new FileInputStream(Os.dup(fd))) {
return dup.read();
}
}
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 7a99b60..311addb 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -29,10 +29,12 @@
"//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
],
libs: [
+ "aatf",
"services.core",
"androidx.annotation_annotation",
],
static_libs: [
+ "a11ychecker-protos-java-proto-lite",
"com_android_server_accessibility_flags_lib",
"//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
@@ -68,3 +70,14 @@
name: "com_android_server_accessibility_flags_lib",
aconfig_declarations: "com_android_server_accessibility_flags",
}
+
+java_library_static {
+ name: "a11ychecker-protos-java-proto-lite",
+ proto: {
+ type: "lite",
+ canonical_path_from_root: false,
+ },
+ srcs: [
+ "java/**/a11ychecker/proto/*.proto",
+ ],
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
new file mode 100644
index 0000000..55af9a0
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckClass;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported;
+import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultType;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateSpeakableTextCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.EditableContentDescCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ImageContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.LinkPurposeUnclearCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.RedundantDescriptionCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TextSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TraversalOrderCheck;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Util class to process a11y checker results for logging.
+ *
+ * @hide
+ */
+public class AccessibilityCheckerUtils {
+
+ private static final String LOG_TAG = "AccessibilityCheckerUtils";
+ @VisibleForTesting
+ // LINT.IfChange
+ static final Map<Class<? extends AccessibilityHierarchyCheck>, AccessibilityCheckClass>
+ CHECK_CLASS_TO_ENUM_MAP =
+ Map.ofEntries(
+ classMapEntry(ClassNameCheck.class, AccessibilityCheckClass.CLASS_NAME_CHECK),
+ classMapEntry(ClickableSpanCheck.class,
+ AccessibilityCheckClass.CLICKABLE_SPAN_CHECK),
+ classMapEntry(DuplicateClickableBoundsCheck.class,
+ AccessibilityCheckClass.DUPLICATE_CLICKABLE_BOUNDS_CHECK),
+ classMapEntry(DuplicateSpeakableTextCheck.class,
+ AccessibilityCheckClass.DUPLICATE_SPEAKABLE_TEXT_CHECK),
+ classMapEntry(EditableContentDescCheck.class,
+ AccessibilityCheckClass.EDITABLE_CONTENT_DESC_CHECK),
+ classMapEntry(ImageContrastCheck.class,
+ AccessibilityCheckClass.IMAGE_CONTRAST_CHECK),
+ classMapEntry(LinkPurposeUnclearCheck.class,
+ AccessibilityCheckClass.LINK_PURPOSE_UNCLEAR_CHECK),
+ classMapEntry(RedundantDescriptionCheck.class,
+ AccessibilityCheckClass.REDUNDANT_DESCRIPTION_CHECK),
+ classMapEntry(SpeakableTextPresentCheck.class,
+ AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK),
+ classMapEntry(TextContrastCheck.class,
+ AccessibilityCheckClass.TEXT_CONTRAST_CHECK),
+ classMapEntry(TextSizeCheck.class, AccessibilityCheckClass.TEXT_SIZE_CHECK),
+ classMapEntry(TouchTargetSizeCheck.class,
+ AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK),
+ classMapEntry(TraversalOrderCheck.class,
+ AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
+ // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
+
+ static Set<AccessibilityCheckResultReported> processResults(
+ Context context,
+ AccessibilityNodeInfo nodeInfo,
+ List<AccessibilityHierarchyCheckResult> checkResults,
+ @Nullable AccessibilityEvent accessibilityEvent,
+ ComponentName a11yServiceComponentName) {
+ return processResults(nodeInfo, checkResults, accessibilityEvent,
+ context.getPackageManager(), a11yServiceComponentName);
+ }
+
+ @VisibleForTesting
+ static Set<AccessibilityCheckResultReported> processResults(
+ AccessibilityNodeInfo nodeInfo,
+ List<AccessibilityHierarchyCheckResult> checkResults,
+ @Nullable AccessibilityEvent accessibilityEvent,
+ PackageManager packageManager,
+ ComponentName a11yServiceComponentName) {
+ String appPackageName = nodeInfo.getPackageName().toString();
+ AccessibilityCheckResultReported.Builder builder;
+ try {
+ builder = AccessibilityCheckResultReported.newBuilder()
+ .setPackageName(appPackageName)
+ .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
+ .setUiElementPath(AccessibilityNodePathBuilder.createNodePath(nodeInfo))
+ .setActivityName(getActivityName(packageManager, accessibilityEvent))
+ .setWindowTitle(getWindowTitle(nodeInfo))
+ .setSourceComponentName(a11yServiceComponentName.flattenToString())
+ .setSourceVersionCode(
+ getAppVersionCode(packageManager,
+ a11yServiceComponentName.getPackageName()));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Unknown package name", e);
+ return Set.of();
+ }
+
+ return checkResults.stream()
+ .filter(checkResult -> checkResult.getType()
+ == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
+ || checkResult.getType()
+ == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
+ .map(checkResult -> builder.setResultCheckClass(
+ getCheckClass(checkResult)).setResultType(
+ getCheckResultType(checkResult)).setResultId(
+ checkResult.getResultId()).build())
+ .collect(Collectors.toUnmodifiableSet());
+ }
+
+ private static long getAppVersionCode(PackageManager packageManager, String packageName) throws
+ PackageManager.NameNotFoundException {
+ PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
+ return packageInfo.getLongVersionCode();
+ }
+
+ /**
+ * Returns the simple class name of the Activity providing the cache update, if available,
+ * or an empty String if not.
+ */
+ @VisibleForTesting
+ static String getActivityName(
+ PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
+ if (accessibilityEvent == null) {
+ return "";
+ }
+ CharSequence activityName = accessibilityEvent.getClassName();
+ if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ && accessibilityEvent.getPackageName() != null
+ && activityName != null) {
+ try {
+ // Check class is for a valid Activity.
+ packageManager
+ .getActivityInfo(
+ new ComponentName(accessibilityEvent.getPackageName().toString(),
+ activityName.toString()), 0);
+ int qualifierEnd = activityName.toString().lastIndexOf('.');
+ return activityName.toString().substring(qualifierEnd + 1);
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to spam the logs. This is very frequent when the class doesn't match
+ // an activity.
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Returns the title of the window containing the a11y node.
+ */
+ private static String getWindowTitle(AccessibilityNodeInfo nodeInfo) {
+ if (nodeInfo.getWindow() == null) {
+ return "";
+ }
+ CharSequence windowTitle = nodeInfo.getWindow().getTitle();
+ return windowTitle == null ? "" : windowTitle.toString();
+ }
+
+ /**
+ * Maps the {@link AccessibilityHierarchyCheck} class that produced the given result, with the
+ * corresponding {@link AccessibilityCheckClass} enum. This enumeration is to avoid relying on
+ * String class names in the logging, which can be proguarded. It also reduces the logging size.
+ */
+ private static AccessibilityCheckClass getCheckClass(
+ AccessibilityHierarchyCheckResult checkResult) {
+ if (CHECK_CLASS_TO_ENUM_MAP.containsKey(checkResult.getSourceCheckClass())) {
+ return CHECK_CLASS_TO_ENUM_MAP.get(checkResult.getSourceCheckClass());
+ }
+ return AccessibilityCheckClass.UNKNOWN_CHECK;
+ }
+
+ private static AccessibilityCheckResultType getCheckResultType(
+ AccessibilityHierarchyCheckResult checkResult) {
+ return switch (checkResult.getType()) {
+ case ERROR -> AccessibilityCheckResultType.ERROR;
+ case WARNING -> AccessibilityCheckResultType.WARNING;
+ default -> AccessibilityCheckResultType.UNKNOWN_RESULT_TYPE;
+ };
+ }
+
+ private static Map.Entry<Class<? extends AccessibilityHierarchyCheck>,
+ AccessibilityCheckClass> classMapEntry(
+ Class<? extends AccessibilityHierarchyCheck> checkClass,
+ AccessibilityCheckClass checkClassEnum) {
+ return new AbstractMap.SimpleImmutableEntry<>(checkClass, checkClassEnum);
+ }
+}
diff --git a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
similarity index 98%
rename from core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
index 2996dde..bbfb217 100644
--- a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/core/java/android/view/accessibility/a11ychecker/OWNERS b/services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
similarity index 100%
rename from core/java/android/view/accessibility/a11ychecker/OWNERS
rename to services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
new file mode 100644
index 0000000..8beed4a
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+package android.accessibility;
+
+option java_package = "com.android.server.accessibility.a11ychecker";
+option java_outer_classname = "A11yCheckerProto";
+
+// TODO(b/326385939): remove and replace usage with the atom extension proto, when submitted.
+/** Logs the result of an AccessibilityCheck. */
+message AccessibilityCheckResultReported {
+ // Package name of the app containing the checked View.
+ optional string package_name = 1;
+ // Version code of the app containing the checked View.
+ optional int64 app_version_code = 2;
+ // The path of the View starting from the root element in the window. Each element is
+ // represented by the View's resource id, when available, or the View's class name.
+ optional string ui_element_path = 3;
+ // Class name of the activity containing the checked View.
+ optional string activity_name = 4;
+ // Title of the window containing the checked View.
+ optional string window_title = 5;
+ // The flattened component name of the app running the AccessibilityService which provided the a11y node.
+ optional string source_component_name = 6;
+ // Version code of the app running the AccessibilityService that provided the a11y node.
+ optional int64 source_version_code = 7;
+ // Class Name of the AccessibilityCheck that produced the result.
+ optional AccessibilityCheckClass result_check_class = 8;
+ // Result type of the AccessibilityCheckResult.
+ optional AccessibilityCheckResultType result_type = 9;
+ // Result ID of the AccessibilityCheckResult.
+ optional int32 result_id = 10;
+}
+
+/** The AccessibilityCheck class. */
+// LINT.IfChange
+enum AccessibilityCheckClass {
+ UNKNOWN_CHECK = 0;
+ CLASS_NAME_CHECK = 1;
+ CLICKABLE_SPAN_CHECK = 2;
+ DUPLICATE_CLICKABLE_BOUNDS_CHECK = 3;
+ DUPLICATE_SPEAKABLE_TEXT_CHECK = 4;
+ EDITABLE_CONTENT_DESC_CHECK = 5;
+ IMAGE_CONTRAST_CHECK = 6;
+ LINK_PURPOSE_UNCLEAR_CHECK = 7;
+ REDUNDANT_DESCRIPTION_CHECK = 8;
+ SPEAKABLE_TEXT_PRESENT_CHECK = 9;
+ TEXT_CONTRAST_CHECK = 10;
+ TEXT_SIZE_CHECK = 11;
+ TOUCH_TARGET_SIZE_CHECK = 12;
+ TRAVERSAL_ORDER_CHECK = 13;
+}
+// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java)
+
+/** The type of AccessibilityCheckResult */
+enum AccessibilityCheckResultType {
+ UNKNOWN_RESULT_TYPE = 0;
+ ERROR = 1;
+ WARNING = 2;
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index d7da2f0..026c69c 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -846,7 +846,6 @@
mCallingAppUid,
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
- event.mFilteredFillabaleViewCount,
event.mViewFillableTotalCount,
event.mViewFillFailureCount,
event.mFocusedId,
@@ -859,7 +858,8 @@
event.mSuggestionPresentedLastTimestampMs,
event.mFocusedVirtualAutofillId,
event.mFieldFirstLength,
- event.mFieldLastLength);
+ event.mFieldLastLength,
+ event.mFilteredFillabaleViewCount);
mEventInternal = Optional.empty();
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 458749d..2d91331 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -185,6 +185,12 @@
final Context mContext;
+ private static final int[] INTERESTING_APP_OPS = new int[] {
+ AppOpsManager.OP_GET_ACCOUNTS,
+ AppOpsManager.OP_READ_CONTACTS,
+ AppOpsManager.OP_WRITE_CONTACTS,
+ };
+
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
private UserManager mUserManager;
@@ -388,74 +394,47 @@
}.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
// Cancel account request notification if an app op was preventing the account access
- mAppOpsManager.startWatchingMode(AppOpsManager.OP_GET_ACCOUNTS, null,
- new AppOpsManager.OnOpChangedInternalListener() {
- @Override
- public void onOpChanged(int op, String packageName) {
- try {
- final int userId = ActivityManager.getCurrentUser();
- final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
- final int mode = mAppOpsManager.checkOpNoThrow(
- AppOpsManager.OP_GET_ACCOUNTS, uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- final long identity = Binder.clearCallingIdentity();
- try {
- UserAccounts accounts = getUserAccounts(userId);
- cancelAccountAccessRequestNotificationIfNeeded(
- packageName, uid, true, accounts);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- } catch (NameNotFoundException e) {
- /* ignore */
- } catch (SQLiteCantOpenDatabaseException e) {
- Log.w(TAG, "Can't read accounts database", e);
- return;
- }
- }
- });
+ for (int i = 0; i < INTERESTING_APP_OPS.length; ++i) {
+ mAppOpsManager.startWatchingMode(INTERESTING_APP_OPS[i], null,
+ new OnInterestingAppOpChangedListener());
+ }
- // Cancel account request notification if a permission was preventing the account access
- mPackageManager.addOnPermissionsChangeListener(
- (int uid) -> {
- // Permission changes cause requires updating accounts cache.
+ // Clear the accounts cache on permission changes.
+ // The specific permissions we care about are backed by AppOps, so just
+ // let the change events on those handle clearing any notifications.
+ mPackageManager.addOnPermissionsChangeListener((int uid) -> {
AccountManager.invalidateLocalAccountsDataCaches();
-
- Account[] accounts = null;
- String[] packageNames = mPackageManager.getPackagesForUid(uid);
- if (packageNames != null) {
- final int userId = UserHandle.getUserId(uid);
- final long identity = Binder.clearCallingIdentity();
- try {
- for (String packageName : packageNames) {
- // if app asked for permission we need to cancel notification even
- // for O+ applications.
- if (mPackageManager.checkPermission(
- Manifest.permission.GET_ACCOUNTS,
- packageName) != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
-
- if (accounts == null) {
- accounts = getAccountsOrEmptyArray(null, userId, "android");
- if (ArrayUtils.isEmpty(accounts)) {
- return;
- }
- }
- UserAccounts userAccounts = getUserAccounts(UserHandle.getUserId(uid));
- for (Account account : accounts) {
- cancelAccountAccessRequestNotificationIfNeeded(
- account, uid, packageName, true, userAccounts);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
});
}
+ private class OnInterestingAppOpChangedListener implements AppOpsManager.OnOpChangedListener {
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ final int userId = ActivityManager.getCurrentUser();
+ final int packageUid;
+ try {
+ packageUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException e) {
+ /* ignore */
+ return;
+ }
+
+ final int mode = mAppOpsManager.checkOpNoThrow(op, packageUid, packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ cancelAccountAccessRequestNotificationIfNeeded(
+ packageName, packageUid, true, getUserAccounts(userId));
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.w(TAG, "Can't read accounts database", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
boolean getBindInstantServiceAllowed(int userId) {
return mAuthenticatorCache.getBindInstantServiceAllowed(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a84306b..0d309eb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,8 +739,6 @@
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
- private final Executor mAudioServerLifecycleExecutor;
-
private IMediaProjectionManager mProjectionService; // to validate projection token
/** Interface for UserManagerService. */
@@ -1061,8 +1059,7 @@
audioserverPermissions() ?
initializeAudioServerPermissionProvider(
context, audioPolicyFacade, audioserverLifecycleExecutor) :
- null,
- audioserverLifecycleExecutor
+ null
);
}
@@ -1148,16 +1145,13 @@
* {@link AudioSystemThread} is created as the messaging thread instead.
* @param appOps {@link AppOpsManager} system service
* @param enforcer Used for permission enforcing
- * @param permissionProvider Used to push permissions to audioserver
- * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
- /* @NonNull */ AudioServerPermissionProvider permissionProvider,
- Executor audioserverLifecycleExecutor) {
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
@@ -1165,7 +1159,6 @@
mAppOps = appOps;
mPermissionProvider = permissionProvider;
- mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -1177,34 +1170,6 @@
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
- // Listen to permission invalidations for the PermissionProvider
- if (audioserverPermissions()) {
- final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
- mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
- new Runnable() {
- // Roughly chosen to be long enough to suppress the autocork behavior
- // of the permission cache (50ms), and longer than the task could reasonably
- // take, even with many packages and users, while not introducing visible
- // permission leaks - since the app needs to restart, and trigger an action
- // which requires permissions from audioserver before this delay.
- // For RECORD_AUDIO, we are additionally protected by appops.
- final long UPDATE_DELAY_MS = 110;
- final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
- @Override
- public void run() {
- var currentTime = SystemClock.uptimeMillis();
- if (currentTime > scheduledUpdateTimestamp.get()) {
- scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
- broadcastHandler.postAtTime( () ->
- mAudioServerLifecycleExecutor.execute(mPermissionProvider
- ::onPermissionStateChanged),
- currentTime + UPDATE_DELAY_MS
- );
- }
- }
- });
- }
-
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -12000,6 +11965,29 @@
provider.onServiceStart(audioPolicy.getPermissionController());
});
+ // Set up event listeners
+ // Must be kept in sync with PermissionManager
+ Runnable cacheSysPropHandler = new Runnable() {
+ private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
+ private AtomicLong mNonce = new AtomicLong();
+ @Override
+ public void run() {
+ if (mHandle.get() == null) {
+ // Cache the handle
+ mHandle.compareAndSet(null, SystemProperties.find(
+ PermissionManager.CACHE_KEY_PACKAGE_INFO));
+ }
+ long nonce;
+ SystemProperties.Handle ref;
+ if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
+ mNonce.getAndSet(nonce) != nonce) {
+ audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
+ }
+ }
+ };
+
+ SystemProperties.addChangeCallback(cacheSysPropHandler);
+
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d083c68..7f4bc74 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,10 +748,6 @@
return AudioSystem.setMasterMute(mute);
}
- public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
- AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
- }
-
/**
* Part of AudioService dump
* @param pw
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d3b41b8..e9ecfc6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -25,6 +25,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.feature.flags.FeatureFlags;
import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
import android.os.Handler;
@@ -614,7 +615,11 @@
&& isBootCompleted
&& !mFoldSettingProvider.shouldStayAwakeOnFold();
} else {
- return mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
+ return currentState.getIdentifier()
+ != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+ && pendingState.getIdentifier()
+ != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
+ && mDeviceStatesOnWhichToSelectiveSleep.get(pendingState.getIdentifier())
&& !mDeviceStatesOnWhichToSelectiveSleep.get(currentState.getIdentifier())
&& isInteractive
&& isBootCompleted
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 5ff421a..7c93c8b 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -89,8 +89,8 @@
void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
@InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
if (DEBUG) {
@@ -128,9 +128,9 @@
void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
@UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
- final IInputMethodInvoker curMethod = bindingController.getCurMethod();
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
@@ -171,8 +171,8 @@
void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
@ImeVisibilityStateComputer.VisibilityState int state,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = mService.getInputMethodBindingController(userId);
final var userData = mService.getUserData(userId);
+ final var bindingController = userData.mBindingController;
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
switch (state) {
case STATE_SHOW_IME:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c093c22..8fd2033 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -476,7 +476,7 @@
@AnyThread
@NonNull
- UserDataRepository.UserData getUserData(@UserIdInt int userId) {
+ UserData getUserData(@UserIdInt int userId) {
return mUserDataRepository.getOrCreate(userId);
}
@@ -1422,15 +1422,15 @@
* Returns true iff the caller is identified to be the current input method with the token.
*
* @param token the window token given to the input method when it was started
- * @param userId userId of the calling IME process
+ * @param userData {@link UserData} of the calling IME process
* @return true if and only if non-null valid token is specified
*/
@GuardedBy("ImfLock.class")
- private boolean calledWithValidTokenLocked(@NonNull IBinder token, @UserIdInt int userId) {
+ private boolean calledWithValidTokenLocked(@NonNull IBinder token, @NonNull UserData userData) {
if (token == null) {
throw new InvalidParameterException("token must not be null.");
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (token != bindingController.getCurToken()) {
Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
+ " uid:" + Binder.getCallingUid() + " token:" + token);
@@ -1709,7 +1709,7 @@
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> clientRemovedForUser =
+ @SuppressWarnings("GuardedBy") Consumer<UserData> clientRemovedForUser =
userData -> onClientRemovedInternalLocked(client, userData);
mUserDataRepository.forAllUserData(clientRemovedForUser);
}
@@ -1719,15 +1719,14 @@
*/
// TODO(b/325515685): Move this method to InputMethodBindingController
@GuardedBy("ImfLock.class")
- private void onClientRemovedInternalLocked(ClientState client,
- @NonNull UserDataRepository.UserData userData) {
+ private void onClientRemovedInternalLocked(ClientState client, @NonNull UserData userData) {
final int userId = userData.mUserId;
if (userData.mCurClient == client) {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
- final var userBindingController = getInputMethodBindingController(userId);
+ final var userBindingController = userData.mBindingController;
IInputMethodInvoker curMethod = userBindingController.getCurMethod();
if (curMethod != null) {
// When we unbind input, we are unbinding the client, so we always
@@ -1759,7 +1758,7 @@
Slog.v(TAG, "unbindCurrentInputLocked: client="
+ userData.mCurClient.mClient.asBinder());
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
@@ -1845,8 +1844,8 @@
@NonNull
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial,
@UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (!userData.mBoundToMethod) {
bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding);
userData.mBoundToMethod = true;
@@ -2275,9 +2274,10 @@
+ bindingController.getCurTokenDisplayId());
}
final int userId = bindingController.getUserId();
+ final var userData = getUserData(userId);
inputMethod.initializeInternal(token,
- new InputMethodPrivilegedOperationsImpl(this, token, userId),
- getInputMethodNavButtonFlagsLocked(getUserData(userId)));
+ new InputMethodPrivilegedOperationsImpl(this, token, userData),
+ getInputMethodNavButtonFlagsLocked(userData));
}
@AnyThread
@@ -2317,8 +2317,8 @@
channel.dispose();
return;
}
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null && method != null
&& curMethod.asBinder() == method.asBinder()) {
@@ -2531,9 +2531,10 @@
@BinderThread
private void updateStatusIcon(@NonNull IBinder token, String packageName,
- @DrawableRes int iconId, @UserIdInt int userId) {
+ @DrawableRes int iconId, @NonNull UserData userData) {
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -2576,8 +2577,7 @@
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked(
- @NonNull UserDataRepository.UserData userData) {
+ private int getInputMethodNavButtonFlagsLocked(@NonNull UserData userData) {
final int userId = userData.mUserId;
final var bindingController = userData.mBindingController;
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
@@ -2672,14 +2672,15 @@
@BinderThread
@SuppressWarnings("deprecation")
private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
// Skip update IME status when current token display is not same as focused display.
// Note that we still need to update IME status when focusing external display
// that does not support system decoration and fallback to show IME on default
@@ -2711,9 +2712,9 @@
@BinderThread
private void reportStartInput(@NonNull IBinder token, IBinder startInputToken,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
@@ -2747,8 +2748,8 @@
@GuardedBy("ImfLock.class")
private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
if (curToken == null) {
return;
@@ -2846,11 +2847,11 @@
settings.putSelectedInputMethod(id);
}
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
bindingController.setSelectedMethodId(id);
// Also re-initialize controllers.
- final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
}
@@ -2887,7 +2888,8 @@
}
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
@@ -2927,7 +2929,6 @@
resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId);
}
- final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
sendOnNavButtonFlagsChangedLocked(userData);
@@ -3409,8 +3410,8 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
bindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
@@ -3543,7 +3544,8 @@
boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
return false;
}
@@ -3556,7 +3558,6 @@
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
- final var userData = getUserData(userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
&& (isInputShownLocked()
@@ -3636,6 +3637,7 @@
Slog.w(TAG, "User #" + userId + " is not running.");
return InputBindResult.INVALID_USER;
}
+ final var userData = getUserData(userId);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"IMMS.startInputOrWindowGainedFocus");
@@ -3643,7 +3645,7 @@
"InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
@@ -3708,7 +3710,6 @@
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
final boolean showForced = mVisibilityStateComputer.mShowForced;
- final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken
&& showForced && shouldClearFlag) {
mVisibilityStateComputer.mShowForced = false;
@@ -3970,13 +3971,9 @@
@BinderThread
private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
- @UserIdInt int userId) {
- userId = mActivityManagerInternal.handleIncomingUser(
- Binder.getCallingPid(), Binder.getCallingUid(), userId, false,
- ActivityManagerInternal.ALLOW_FULL_ONLY, "onImeSwitchButtonClickFromClient", null);
-
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
showInputMethodPickerFromSystem(
@@ -3998,10 +3995,11 @@
}
@BinderThread
- private void setInputMethod(@NonNull IBinder token, String id, @UserIdInt int userId) {
+ private void setInputMethod(@NonNull IBinder token, String id, @NonNull UserData userData) {
final int callingUid = Binder.getCallingUid();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4016,10 +4014,11 @@
@BinderThread
private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
- InputMethodSubtype subtype, @UserIdInt int userId) {
+ InputMethodSubtype subtype, @NonNull UserData userData) {
final int callingUid = Binder.getCallingUid();
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -4032,18 +4031,20 @@
setInputMethodWithSubtypeIdLocked(token, id,
SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
} else {
- setInputMethod(token, id, userId);
+ setInputMethod(token, id, userData);
}
}
}
@BinderThread
- private boolean switchToPreviousInputMethod(@NonNull IBinder token, @UserIdInt int userId) {
+ private boolean switchToPreviousInputMethod(@NonNull IBinder token,
+ @NonNull UserData userData) {
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
@@ -4120,21 +4121,21 @@
@BinderThread
private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- return switchToNextInputMethodLocked(token, onlyCurrentIme, userId);
+ return switchToNextInputMethodLocked(token, onlyCurrentIme, userData);
}
}
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
- @UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
+ @NonNull UserData userData) {
+ final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(onlyCurrentIme, currentImi,
bindingController.getCurrentSubtype(),
MODE_AUTO, true /* forward */);
@@ -4142,20 +4143,20 @@
return false;
}
setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
- nextSubtype.mSubtypeId, userId);
+ nextSubtype.mSubtypeId, userData.mUserId);
return true;
}
@BinderThread
private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return false;
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
+ final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
.getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
bindingController.getCurrentSubtype(),
MODE_AUTO, true /* forward */);
@@ -4556,8 +4557,8 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
@@ -4588,12 +4589,12 @@
}
@BinderThread
- private void notifyUserAction(@NonNull IBinder token, @UserIdInt int userId) {
+ private void notifyUserAction(@NonNull IBinder token, @NonNull UserData userData) {
if (DEBUG) {
Slog.d(TAG, "Got the notification of a user action.");
}
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getCurToken() != token) {
if (DEBUG) {
Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
@@ -4603,7 +4604,7 @@
}
final InputMethodInfo imi = bindingController.getSelectedMethod();
if (imi != null) {
- getUserData(userId).mSwitchingController.onUserActionLocked(imi,
+ userData.mSwitchingController.onUserActionLocked(imi,
bindingController.getCurrentSubtype());
}
}
@@ -4611,11 +4612,12 @@
@BinderThread
private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
- @NonNull ImeTracker.Token statsToken, @UserIdInt int userId) {
+ @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
@@ -4690,8 +4692,8 @@
@UserIdInt int userId) {
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken,
userId);
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
show, userData.mImeBindingState.mFocusedWindow, requestToken,
@@ -4711,16 +4713,16 @@
@BinderThread
private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
- final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
@@ -4750,16 +4752,16 @@
@BinderThread
private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
+ final int userId = userData.mUserId;
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
- final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
@@ -4804,8 +4806,7 @@
}
@GuardedBy("ImfLock.class")
- void setEnabledSessionLocked(SessionState session,
- @NonNull UserDataRepository.UserData userData) {
+ void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) {
if (userData.mEnabledSession != session) {
if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
if (DEBUG) Slog.v(TAG, "Disabling: " + userData.mEnabledSession);
@@ -4824,7 +4825,7 @@
@GuardedBy("ImfLock.class")
void setEnabledSessionForAccessibilityLocked(
SparseArray<AccessibilitySessionState> accessibilitySessions,
- @NonNull UserDataRepository.UserData userData) {
+ @NonNull UserData userData) {
// mEnabledAccessibilitySessions could the same object as accessibilitySessions.
SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
@@ -5033,9 +5034,8 @@
case MSG_START_HANDWRITING:
final var handwritingRequest = (HandwritingRequest) msg.obj;
synchronized (ImfLock.class) {
- final int userId = handwritingRequest.userId;
- final var bindingController = getInputMethodBindingController(userId);
- final var userData = getUserData(userId);
+ final var userData = handwritingRequest.userData;
+ final var bindingController = userData.mBindingController;
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) {
return true;
@@ -5080,24 +5080,24 @@
return false;
}
- private record HandwritingRequest(int requestId, int pid, @UserIdInt int userId) { }
+ private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
@BinderThread
- private void onStylusHandwritingReady(int requestId, int pid, @UserIdInt int userId) {
+ private void onStylusHandwritingReady(int requestId, int pid, @NonNull UserData userData) {
mHandler.obtainMessage(MSG_START_HANDWRITING,
- new HandwritingRequest(requestId, pid, userId)).sendToTarget();
+ new HandwritingRequest(requestId, pid, userData)).sendToTarget();
}
private void handleSetInteractive(final boolean interactive) {
synchronized (ImfLock.class) {
// TODO(b/305849394): Support multiple IMEs.
final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
mIsInteractive = interactive;
updateSystemUiLocked(
interactive ? bindingController.getImeWindowVis() : 0,
bindingController.getBackDisposition(), userId);
- final var userData = getUserData(userId);
// Inform the current client of the change in active status
if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
return;
@@ -5329,7 +5329,7 @@
}
@GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+ void sendOnNavButtonFlagsChangedLocked(@NonNull UserData userData) {
final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
@@ -5345,7 +5345,7 @@
final boolean value =
InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
- final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+ final ArrayList<UserData> updatedUsers = new ArrayList<>();
for (int profileUserId : profileUserIds) {
final var userData = getUserData(profileUserId);
userData.mImeDrawsNavBar.set(value);
@@ -5598,10 +5598,11 @@
}
@GuardedBy("ImfLock.class")
- private void switchKeyboardLayoutLocked(int direction, @UserIdInt int userId) {
+ private void switchKeyboardLayoutLocked(int direction, @NonNull UserData userData) {
+ final int userId = userData.mUserId;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
final InputMethodInfo currentImi = settings.getMethodMap().get(
bindingController.getSelectedMethodId());
if (currentImi == null) {
@@ -5610,7 +5611,7 @@
final var currentSubtype = bindingController.getCurrentSubtype();
final InputMethodSubtypeHandle nextSubtypeHandle;
if (Flags.imeSwitcherRevamp()) {
- final var nextItem = getUserData(userId).mSwitchingController
+ final var nextItem = userData.mSwitchingController
.getNextInputMethodForHardware(
false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO,
direction > 0 /* forward */);
@@ -5626,8 +5627,7 @@
} else {
final InputMethodSubtypeHandle currentSubtypeHandle =
InputMethodSubtypeHandle.of(currentImi, currentSubtype);
- nextSubtypeHandle =
- getUserData(userId).mHardwareKeyboardShortcutController.onSubtypeSwitch(
+ nextSubtypeHandle = userData.mHardwareKeyboardShortcutController.onSubtypeSwitch(
currentSubtypeHandle, direction > 0);
}
if (nextSubtypeHandle == null) {
@@ -5775,7 +5775,7 @@
if (displayId != bindingController.getCurTokenDisplayId()) {
return false;
}
- curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken();
+ curHostInputToken = bindingController.getCurHostInputToken();
if (curHostInputToken == null) {
return false;
}
@@ -5831,8 +5831,8 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (userData.mCurClient != null) {
clearClientSessionForAccessibilityLocked(userData.mCurClient,
@@ -5869,8 +5869,8 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(userId);
final var userData = getUserData(userId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (userData.mCurClient != null) {
if (DEBUG) {
@@ -5914,14 +5914,14 @@
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
// TODO(b/305849394): Infer userId from displayId
- switchKeyboardLayoutLocked(direction, mCurrentUserId);
+ switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
}
}
}
@BinderThread
private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
- @Nullable Uri contentUri, @Nullable String packageName, @UserIdInt int imeUserId) {
+ @Nullable Uri contentUri, @Nullable String packageName, @NonNull UserData userData) {
if (token == null) {
throw new NullPointerException("token");
}
@@ -5938,7 +5938,7 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
- final var bindingController = getInputMethodBindingController(imeUserId);
+ final var bindingController = userData.mBindingController;
if (bindingController.getSelectedMethodId() == null) {
return null;
}
@@ -5950,7 +5950,6 @@
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- final var userData = getUserData(imeUserId);
final var curPackageName = userData.mCurEditorInfo != null
? userData.mCurEditorInfo.packageName : null;
if (!TextUtils.equals(curPackageName, packageName)) {
@@ -5962,7 +5961,7 @@
final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
// This user ID may be invalid if "contentUri" embedded an invalid user ID.
final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
- imeUserId);
+ userData.mUserId);
final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
// Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
// actually has the right to grant a read permission for "contentUriWithoutUserId" that
@@ -5977,12 +5976,11 @@
@BinderThread
private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
- @UserIdInt int userId) {
+ @NonNull UserData userData) {
synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userId)) {
+ if (!calledWithValidTokenLocked(token, userData)) {
return;
}
- final var userData = getUserData(userId);
if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
userData.mInFullscreenMode = fullscreen;
userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
@@ -6109,8 +6107,8 @@
p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
- p.println(" mCurrentUserId=" + mCurrentUserId);
+ final var bindingController = userData.mBindingController;
+ p.println(" mCurrentUserId=" + userData.mUserId);
p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
client = userData.mCurClient;
p.println(" mCurClient=" + client + " mCurSeq="
@@ -6125,7 +6123,7 @@
p.println(" mUserDataRepository=");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
- @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump =
+ @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
u -> {
p.println(" mUserId=" + u.mUserId);
p.println(" hasMainConnection="
@@ -6662,7 +6660,7 @@
0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
}
- final var bindingController = getInputMethodBindingController(userId);
+ final var bindingController = userData.mBindingController;
bindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
@@ -6810,26 +6808,26 @@
private final InputMethodManagerService mImms;
@NonNull
private final IBinder mToken;
- @UserIdInt
- private final int mUserId;
+ @NonNull
+ private final UserData mUserData;
InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
- @NonNull IBinder token, @UserIdInt int userId) {
+ @NonNull IBinder token, @NonNull UserData userData) {
mImms = imms;
mToken = token;
- mUserId = userId;
+ mUserData = userData;
}
@BinderThread
@Override
public void setImeWindowStatusAsync(int vis, int backDisposition) {
- mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserId);
+ mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserData);
}
@BinderThread
@Override
public void reportStartInputAsync(IBinder startInputToken) {
- mImms.reportStartInput(mToken, startInputToken, mUserId);
+ mImms.reportStartInput(mToken, startInputToken, mUserData);
}
@BinderThread
@@ -6845,7 +6843,7 @@
@SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
try {
typedFuture.complete(mImms.createInputContentUriToken(
- mToken, contentUri, packageName, mUserId).asBinder());
+ mToken, contentUri, packageName, mUserData).asBinder());
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6854,7 +6852,7 @@
@BinderThread
@Override
public void reportFullscreenModeAsync(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen, mUserId);
+ mImms.reportFullscreenMode(mToken, fullscreen, mUserData);
}
@BinderThread
@@ -6862,7 +6860,7 @@
public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethod(mToken, id, mUserId);
+ mImms.setInputMethod(mToken, id, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6875,7 +6873,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserId);
+ mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6889,7 +6887,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserId);
+ mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6903,7 +6901,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserId);
+ mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserData);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6913,7 +6911,7 @@
@BinderThread
@Override
public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
- mImms.updateStatusIcon(mToken, packageName, iconId, mUserId);
+ mImms.updateStatusIcon(mToken, packageName, iconId, mUserData);
}
@BinderThread
@@ -6921,7 +6919,7 @@
public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserId));
+ typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6934,7 +6932,7 @@
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
- mUserId));
+ mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6945,7 +6943,8 @@
public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken, mUserId));
+ typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken,
+ mUserData));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6953,27 +6952,27 @@
@BinderThread
@Override
- public void onImeSwitchButtonClickFromClient(int displayId, @UserIdInt int userId) {
- mImms.onImeSwitchButtonClickFromClient(mToken, displayId, userId);
+ public void onImeSwitchButtonClickFromClient(int displayId) {
+ mImms.onImeSwitchButtonClickFromClient(mToken, displayId, mUserData);
}
@BinderThread
@Override
public void notifyUserActionAsync() {
- mImms.notifyUserAction(mToken, mUserId);
+ mImms.notifyUserAction(mToken, mUserData);
}
@BinderThread
@Override
public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
@NonNull ImeTracker.Token statsToken) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserId);
+ mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserData);
}
@BinderThread
@Override
public void onStylusHandwritingReady(int requestId, int pid) {
- mImms.onStylusHandwritingReady(requestId, pid, mUserId);
+ mImms.onStylusHandwritingReady(requestId, pid, mUserData);
}
@BinderThread
@@ -6986,12 +6985,12 @@
@Override
public void switchKeyboardLayoutAsync(int direction) {
synchronized (ImfLock.class) {
- if (!mImms.calledWithValidTokenLocked(mToken, mUserId)) {
+ if (!mImms.calledWithValidTokenLocked(mToken, mUserData)) {
return;
}
final long ident = Binder.clearCallingIdentity();
try {
- mImms.switchKeyboardLayoutLocked(direction, mUserId);
+ mImms.switchKeyboardLayoutLocked(direction, mUserData);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
new file mode 100644
index 0000000..ec5c9e6
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.SparseArray;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Placeholder for all IMMS user specific fields */
+final class UserData {
+ @UserIdInt
+ final int mUserId;
+
+ @NonNull
+ final InputMethodBindingController mBindingController;
+
+ @NonNull
+ final InputMethodSubtypeSwitchingController mSwitchingController =
+ new InputMethodSubtypeSwitchingController();
+
+ @NonNull
+ final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController();
+
+ /**
+ * Have we called mCurMethod.bindInput()?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToMethod = false;
+
+ /**
+ * Have we called bindInput() for accessibility services?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ClientState mCurClient = null;
+
+ @GuardedBy("ImfLock.class")
+ boolean mInFullscreenMode;
+
+ /**
+ * The {@link IRemoteInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteInputConnection mCurInputConnection;
+
+ /**
+ * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+ * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+ /**
+ * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+
+ /**
+ * The {@link EditorInfo} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ EditorInfo mCurEditorInfo;
+
+ /**
+ * The token tracking the current IME show request that is waiting for a connection to an
+ * IME, otherwise {@code null}.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeTracker.Token mCurStatsToken;
+
+ /**
+ * Currently enabled session.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodManagerService.SessionState mEnabledSession;
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ SparseArray<InputMethodManagerService.AccessibilitySessionState>
+ mEnabledAccessibilitySessions = new SparseArray<>();
+
+ /**
+ * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
+ */
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ String mLastEnabledInputMethodsStr = "";
+
+ /**
+ * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+ */
+ @NonNull
+ final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
+ * Intended to be instantiated only from this file.
+ */
+ UserData(@UserIdInt int userId,
+ @NonNull InputMethodBindingController bindingController) {
+ mUserId = userId;
+ mBindingController = bindingController;
+ }
+
+ @Override
+ public String toString() {
+ return "UserData{" + "mUserId=" + mUserId + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 7c68d54..6f831cc 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -18,18 +18,11 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.util.SparseArray;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ImeTracker;
-import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -87,120 +80,4 @@
mUserDataLock.writeLock().unlock();
}
}
-
- /** Placeholder for all IMMS user specific fields */
- static final class UserData {
- @UserIdInt
- final int mUserId;
-
- @NonNull
- final InputMethodBindingController mBindingController;
-
- @NonNull
- final InputMethodSubtypeSwitchingController mSwitchingController;
-
- @NonNull
- final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
-
- /**
- * Have we called mCurMethod.bindInput()?
- */
- @GuardedBy("ImfLock.class")
- boolean mBoundToMethod = false;
-
- /**
- * Have we called bindInput() for accessibility services?
- */
- @GuardedBy("ImfLock.class")
- boolean mBoundToAccessibility;
-
- @GuardedBy("ImfLock.class")
- @NonNull
- ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
-
- @GuardedBy("ImfLock.class")
- @Nullable
- ClientState mCurClient = null;
-
- @GuardedBy("ImfLock.class")
- boolean mInFullscreenMode;
-
- /**
- * The {@link IRemoteInputConnection} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IRemoteInputConnection mCurInputConnection;
-
- /**
- * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
- * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- ImeOnBackInvokedDispatcher mCurImeDispatcher;
-
- /**
- * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
-
- /**
- * The {@link EditorInfo} last provided by the current client.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- EditorInfo mCurEditorInfo;
-
- /**
- * The token tracking the current IME show request that is waiting for a connection to an
- * IME, otherwise {@code null}.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- ImeTracker.Token mCurStatsToken;
-
- /**
- * Currently enabled session.
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- InputMethodManagerService.SessionState mEnabledSession;
-
- @GuardedBy("ImfLock.class")
- @Nullable
- SparseArray<InputMethodManagerService.AccessibilitySessionState>
- mEnabledAccessibilitySessions = new SparseArray<>();
-
- /**
- * A per-user cache of {@link InputMethodSettings#getEnabledInputMethodsStr()}.
- */
- @GuardedBy("ImfLock.class")
- @NonNull
- String mLastEnabledInputMethodsStr = "";
-
- /**
- * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
- */
- @NonNull
- final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
-
- /**
- * Intended to be instantiated only from this file.
- */
- private UserData(@UserIdInt int userId,
- @NonNull InputMethodBindingController bindingController) {
- mUserId = userId;
- mBindingController = bindingController;
- mSwitchingController = new InputMethodSubtypeSwitchingController();
- mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController();
- }
-
- @Override
- public String toString() {
- return "UserData{" + "mUserId=" + mUserId + '}';
- }
- }
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 4851a81..3d0b079 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -48,6 +48,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.FrameworkStatsLog;
@@ -100,6 +101,7 @@
private LocaleManagerBackupHelper mBackupHelper;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor;
private final Object mWriteLock = new Object();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9e53cc3..016abff 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,8 +110,8 @@
import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationForceGrouping;
-import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
+import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -248,7 +248,6 @@
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.drawable.Icon;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
@@ -373,7 +372,6 @@
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
-import java.util.function.BiPredicate;
import libcore.io.IoUtils;
import org.json.JSONException;
@@ -4634,29 +4632,28 @@
@Override
public List<String> getPackagesBypassingDnd(int userId,
- boolean includeConversationChannels) {
+ boolean includeConversationChannels) {
checkCallerIsSystem();
final ArraySet<String> packageNames = new ArraySet<>();
- for (int user : mUm.getProfileIds(userId, false)) {
- List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, user);
- for (PackageInfo pi : pkgs) {
- String pkg = pi.packageName;
- // If any NotificationChannel for this package is bypassing, the
- // package is considered bypassing.
- for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
- pi.applicationInfo.uid).getList()) {
- // Skips non-demoted conversation channels.
- if (!includeConversationChannels
- && !TextUtils.isEmpty(channel.getConversationId())
- && !channel.isDemoted()) {
- continue;
- }
- packageNames.add(pkg);
+ List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
+ for (PackageInfo pi : pkgs) {
+ String pkg = pi.packageName;
+ // If any NotificationChannel for this package is bypassing, the
+ // package is considered bypassing.
+ for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
+ pi.applicationInfo.uid).getList()) {
+ // Skips non-demoted conversation channels.
+ if (!includeConversationChannels
+ && !TextUtils.isEmpty(channel.getConversationId())
+ && !channel.isDemoted()) {
+ continue;
}
+ packageNames.add(pkg);
}
}
+
return new ArrayList<String>(packageNames);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 303371b..8d3f07e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2261,6 +2261,12 @@
installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
+
+ // The installation is success, remove the split info copy stored in package
+ // setting for the downgrade version check of DELETE_KEEP_DATA and archived app
+ // cases.
+ ps.setSplitNames(null);
+ ps.setSplitRevisionCodes(null);
}
installRequest.onCommitFinished();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index c3cac20..a1dffc6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1423,11 +1423,8 @@
*/
public static void checkDowngrade(@NonNull PackageSetting before,
@NonNull PackageInfoLite after) throws PackageManagerException {
- if (after.getLongVersionCode() < before.getVersionCode()) {
- throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
- "Update version code " + after.versionCode + " is older than current "
- + before.getVersionCode());
- }
+ checkDowngrade(before.getVersionCode(), before.getBaseRevisionCode(),
+ before.getSplitNames(), before.getSplitRevisionCodes(), after);
}
/**
@@ -1436,28 +1433,35 @@
*/
public static void checkDowngrade(@NonNull AndroidPackage before,
@NonNull PackageInfoLite after) throws PackageManagerException {
- if (after.getLongVersionCode() < before.getLongVersionCode()) {
+ checkDowngrade(before.getLongVersionCode(), before.getBaseRevisionCode(),
+ before.getSplitNames(), before.getSplitRevisionCodes(), after);
+ }
+
+ private static void checkDowngrade(long beforeVersionCode, int beforeBaseRevisionCode,
+ @NonNull String[] beforeSplitNames, @NonNull int[] beforeSplitRevisionCodes,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
+ if (after.getLongVersionCode() < beforeVersionCode) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update version code " + after.versionCode + " is older than current "
- + before.getLongVersionCode());
- } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
- if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+ + beforeVersionCode);
+ } else if (after.getLongVersionCode() == beforeVersionCode) {
+ if (after.baseRevisionCode < beforeBaseRevisionCode) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update base revision code " + after.baseRevisionCode
- + " is older than current " + before.getBaseRevisionCode());
+ + " is older than current " + beforeBaseRevisionCode);
}
if (!ArrayUtils.isEmpty(after.splitNames)) {
for (int i = 0; i < after.splitNames.length; i++) {
final String splitName = after.splitNames[i];
- final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+ final int j = ArrayUtils.indexOf(beforeSplitNames, splitName);
if (j != -1) {
- if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+ if (after.splitRevisionCodes[i] < beforeSplitRevisionCodes[j]) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update split " + splitName + " revision code "
+ after.splitRevisionCodes[i]
+ " is older than current "
- + before.getSplitRevisionCodes()[j]);
+ + beforeSplitRevisionCodes[j]);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 82df527..9f10e01 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -234,6 +234,22 @@
@Nullable
private byte[] mRestrictUpdateHash;
+ // This is the copy of the same data stored in AndroidPackage. It is not null if the
+ // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+ // the field will be null, and the getter method will return the data from AndroidPackage
+ // instead.
+ @Nullable
+ private String[] mSplitNames;
+
+ // This is the copy of the same data stored in AndroidPackage. It is not null if the
+ // AndroidPackage is deleted in cases of DELETE_KEEP_DATA. When AndroidPackage is not null,
+ // the field will be null, and the getter method will return the data from AndroidPackage
+ // instead.
+ @Nullable
+ private int[] mSplitRevisionCodes;
+
+ private int mBaseRevisionCode;
+
/**
* Snapshot support.
*/
@@ -578,6 +594,62 @@
return getBoolean(Booleans.DEBUGGABLE);
}
+ /**
+ * @see AndroidPackage#getBaseRevisionCode
+ */
+ public PackageSetting setBaseRevisionCode(int value) {
+ mBaseRevisionCode = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getBaseRevisionCode
+ */
+ public int getBaseRevisionCode() {
+ return mBaseRevisionCode;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitNames
+ */
+ public PackageSetting setSplitNames(String[] value) {
+ mSplitNames = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitNames
+ */
+ @NonNull
+ public String[] getSplitNames() {
+ if (pkg != null) {
+ return pkg.getSplitNames();
+ }
+ return mSplitNames == null ? EmptyArray.STRING : mSplitNames;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitRevisionCodes
+ */
+ public PackageSetting setSplitRevisionCodes(int[] value) {
+ mSplitRevisionCodes = value;
+ onChanged();
+ return this;
+ }
+
+ /**
+ * @see AndroidPackage#getSplitRevisionCodes
+ */
+ @NonNull
+ public int[] getSplitRevisionCodes() {
+ if (pkg != null) {
+ return pkg.getSplitRevisionCodes();
+ }
+ return mSplitRevisionCodes == null ? EmptyArray.INT : mSplitRevisionCodes;
+ }
+
@Override
public String toString() {
return "PackageSetting{"
@@ -739,6 +811,11 @@
mTargetSdkVersion = other.mTargetSdkVersion;
mRestrictUpdateHash = other.mRestrictUpdateHash == null
? null : other.mRestrictUpdateHash.clone();
+ mBaseRevisionCode = other.mBaseRevisionCode;
+ mSplitNames = other.mSplitNames != null
+ ? Arrays.copyOf(other.mSplitNames, other.mSplitNames.length) : null;
+ mSplitRevisionCodes = other.mSplitRevisionCodes != null
+ ? Arrays.copyOf(other.mSplitRevisionCodes, other.mSplitRevisionCodes.length) : null;
usesSdkLibraries = other.usesSdkLibraries != null
? Arrays.copyOf(other.usesSdkLibraries,
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 2f2c451..7afc358 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -432,6 +432,13 @@
}
deletedPs.setInstalled(/* installed= */ false, userId);
}
+
+ // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
+ // app cases
+ if (deletedPkg.getSplitNames() != null) {
+ deletedPs.setSplitNames(deletedPkg.getSplitNames());
+ deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+ }
}
// make sure to preserve per-user installed state if this removal was just
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index d8ce38e..95561f5f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,8 +437,9 @@
pkgSetting.setIsOrphaned(true);
}
- // update debuggable to packageSetting
+ // update debuggable and BaseRevisionCode to packageSetting
pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+ pkgSetting.setBaseRevisionCode(parsedPackage.getBaseRevisionCode());
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0d16b00..9177e2b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -325,6 +325,7 @@
private static final String TAG_MIME_TYPE = "mime-type";
private static final String TAG_ARCHIVE_STATE = "archive-state";
private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
+ private static final String TAG_SPLIT_VERSION = "split-version";
public static final String ATTR_NAME = "name";
public static final String ATTR_PACKAGE = "package";
@@ -3261,6 +3262,9 @@
if (pkg.isLoading()) {
serializer.attributeBoolean(null, "isLoading", true);
}
+ if (pkg.getBaseRevisionCode() != 0) {
+ serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
+ }
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
@@ -3289,7 +3293,12 @@
writeUpgradeKeySetsLPr(serializer, pkg.getKeySetData());
writeKeySetAliasesLPr(serializer, pkg.getKeySetData());
writeMimeGroupLPr(serializer, pkg.getMimeGroups());
-
+ // If getPkg is not NULL, these values are from the getPkg. And these values are preserved
+ // for the downgrade check for DELETE_KEEP_DATA and archived app cases. If the getPkg is
+ // not NULL, we don't need to preserve it.
+ if (pkg.getPkg() == null) {
+ writeSplitVersionsLPr(serializer, pkg.getSplitNames(), pkg.getSplitRevisionCodes());
+ }
serializer.endTag(null, "package");
}
@@ -4071,6 +4080,7 @@
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
boolean isSdkLibrary = false;
+ int baseRevisionCode = 0;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -4116,6 +4126,7 @@
appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
PackageManager.APP_METADATA_SOURCE_UNKNOWN);
+ baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
"scannedAsStoppedSystemApp", false);
@@ -4269,6 +4280,7 @@
.setAppMetadataFilePath(appMetadataFilePath)
.setAppMetadataSource(appMetadataSource)
.setTargetSdkVersion(targetSdkVersion)
+ .setBaseRevisionCode(baseRevisionCode)
.setRestrictUpdateHash(restrictUpdateHash)
.setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
// Handle legacy string here for single-user mode
@@ -4374,6 +4386,8 @@
readUsesStaticLibLPw(parser, packageSetting);
} else if (tagName.equals(TAG_USES_SDK_LIB)) {
readUsesSdkLibLPw(parser, packageSetting);
+ } else if (tagName.equals(TAG_SPLIT_VERSION)) {
+ readSplitVersionsLPw(parser, packageSetting);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <package>: " + parser.getName());
@@ -4470,6 +4484,37 @@
}
}
+ private void readSplitVersionsLPw(TypedXmlPullParser parser, PackageSetting outPs)
+ throws IOException, XmlPullParserException {
+ String splitName = parser.getAttributeValue(null, ATTR_NAME);
+ int splitRevision = parser.getAttributeInt(null, ATTR_VERSION, -1);
+ if (splitName != null && splitRevision >= 0) {
+ outPs.setSplitNames(ArrayUtils.appendElement(String.class,
+ outPs.getSplitNames(), splitName));
+ outPs.setSplitRevisionCodes(ArrayUtils.appendInt(
+ outPs.getSplitRevisionCodes(), splitRevision));
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ private void writeSplitVersionsLPr(TypedXmlSerializer serializer, String[] splitNames,
+ int[] splitRevisionCodes) throws IOException {
+ if (ArrayUtils.isEmpty(splitNames) || ArrayUtils.isEmpty(splitRevisionCodes)
+ || splitNames.length != splitRevisionCodes.length) {
+ return;
+ }
+ final int libLength = splitNames.length;
+ for (int i = 0; i < libLength; i++) {
+ final String splitName = splitNames[i];
+ final int splitRevision = splitRevisionCodes[i];
+ serializer.startTag(null, TAG_SPLIT_VERSION);
+ serializer.attribute(null, ATTR_NAME, splitName);
+ serializer.attributeInt(null, ATTR_VERSION, splitRevision);
+ serializer.endTag(null, TAG_SPLIT_VERSION);
+ }
+ }
+
private void readDisabledComponentsLPw(PackageSetting packageSetting, TypedXmlPullParser parser,
int userId) throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8dc9756..8419a60 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -933,8 +933,7 @@
public void onWakeUp() {
synchronized (mLock) {
if (shouldEnableWakeGestureLp()) {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
- "Wake Up");
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Wake Up");
mWindowWakeUpPolicy.wakeUpFromWakeGesture();
}
}
@@ -1403,7 +1402,7 @@
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Global Actions");
showGlobalActions();
break;
@@ -1415,14 +1414,14 @@
if (ActivityManager.isUserAMonkey()) {
break;
}
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Shut Off");
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Long Press - Go To Voice Assist");
// Some devices allow the voice assistant intent during setup (and use that intent
// to launch something else, like Settings). So we explicitly allow that via the
@@ -1431,7 +1430,7 @@
break;
case LONG_PRESS_POWER_ASSISTANT:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON,
"Power - Long Press - Go To Assistant");
final int powerKeyDeviceId = INVALID_INPUT_DEVICE_ID;
launchAssistAction(null, powerKeyDeviceId, eventTime,
@@ -1446,7 +1445,7 @@
break;
case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power - Very Long Press - Show Global Actions");
showGlobalActions();
break;
@@ -1599,8 +1598,7 @@
case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
- performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
- false, /* reason = */
+ performHapticFeedback(HapticFeedbackConstants.CONFIRM,
"Stem primary - Triple Press - Toggle Accessibility");
}
break;
@@ -1771,7 +1769,7 @@
@Override
public void run() {
mEndCallKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
"End Call - Long Press - Show Global Actions");
showGlobalActionsInternal();
}
@@ -2087,8 +2085,7 @@
return;
}
mHomeConsumed = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
- "Home - Long Press");
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press");
switch (mLongPressOnHomeBehavior) {
case LONG_PRESS_HOME_ALL_APPS:
if (mHasFeatureLeanback) {
@@ -2530,7 +2527,7 @@
break;
case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
performHapticFeedback(
- HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
+ HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON,
"Power + Volume Up - Global Actions");
showGlobalActions();
mPowerKeyHandled = true;
@@ -5078,8 +5075,7 @@
}
if (useHapticFeedback) {
- performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
- "Virtual Key - Press");
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, "Virtual Key - Press");
}
if (isWakeKey) {
@@ -5971,8 +5967,10 @@
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
if (safeMode) {
- performHapticFeedback(HapticFeedbackConstants.SAFE_MODE_ENABLED, true,
- "Safe Mode Enabled");
+ performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+ HapticFeedbackConstants.SAFE_MODE_ENABLED,
+ "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+ 0 /* privFlags */);
}
}
@@ -6441,9 +6439,9 @@
Settings.Global.THEATER_MODE_ON, 0) == 1;
}
- private boolean performHapticFeedback(int effectId, boolean always, String reason) {
+ private boolean performHapticFeedback(int effectId, String reason) {
return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
- effectId, always, reason, false /* fromIme */);
+ effectId, reason, 0 /* flags */, 0 /* privFlags */);
}
@Override
@@ -6452,8 +6450,8 @@
}
@Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme) {
+ public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+ int flags, int privFlags) {
if (!mVibrator.hasVibrator()) {
return false;
}
@@ -6464,7 +6462,7 @@
}
VibrationAttributes attrs =
mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ always, fromIme);
+ effectId, flags, privFlags);
VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
mVibrator.vibrate(uid, packageName, effect, reason, attrs);
return true;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 6c05d70..1b394f6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import android.view.HapticFeedbackConstants;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -1079,7 +1080,8 @@
* Call from application to perform haptic feedback on its window.
*/
public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme);
+ String reason, @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
/**
* Called when we have started keeping the screen on because a window
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 8138168..98a2ba0d 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -186,13 +186,13 @@
*
* @param effectId the haptic feedback effect ID whose respective vibration attributes we want
* to get.
- * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass
- * vibration intensity settings. {@code false} otherwise.
- * @param fromIme the haptic feedback is performed from an IME.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
* @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
*/
- public VibrationAttributes getVibrationAttributesForHapticFeedback(
- int effectId, boolean bypassVibrationIntensitySetting, boolean fromIme) {
+ public VibrationAttributes getVibrationAttributesForHapticFeedback(int effectId,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
VibrationAttributes attrs;
switch (effectId) {
case HapticFeedbackConstants.EDGE_SQUEEZE:
@@ -208,7 +208,7 @@
break;
case HapticFeedbackConstants.KEYBOARD_TAP:
case HapticFeedbackConstants.KEYBOARD_RELEASE:
- attrs = createKeyboardVibrationAttributes(fromIme);
+ attrs = createKeyboardVibrationAttributes(privFlags);
break;
case HapticFeedbackConstants.BIOMETRIC_CONFIRM:
case HapticFeedbackConstants.BIOMETRIC_REJECT:
@@ -218,18 +218,23 @@
attrs = TOUCH_VIBRATION_ATTRIBUTES;
}
- int flags = 0;
+ int vibFlags = 0;
+ boolean fromIme =
+ (privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) != 0;
+ boolean bypassVibrationIntensitySetting =
+ (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
if (bypassVibrationIntensitySetting) {
- flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
}
if (shouldBypassInterruptionPolicy(effectId)) {
- flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
}
if (shouldBypassIntensityScale(effectId, fromIme)) {
- flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
+ vibFlags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE;
}
- return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
+ return vibFlags == 0 ? attrs : new VibrationAttributes.Builder(attrs)
+ .setFlags(vibFlags).build();
}
/**
@@ -373,12 +378,16 @@
return false;
}
- private VibrationAttributes createKeyboardVibrationAttributes(boolean fromIme) {
- // Use touch attribute when the keyboard category is disable or it's not from an IME.
- if (!Flags.keyboardCategoryEnabled() || !fromIme) {
+ private VibrationAttributes createKeyboardVibrationAttributes(
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ // Use touch attribute when the keyboard category is disable.
+ if (!Flags.keyboardCategoryEnabled()) {
return TOUCH_VIBRATION_ATTRIBUTES;
}
-
+ // Use touch attribute when the haptic is not apply to IME.
+ if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
+ return TOUCH_VIBRATION_ATTRIBUTES;
+ }
return new VibrationAttributes.Builder(TOUCH_VIBRATION_ATTRIBUTES)
.setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
.build();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5c15ccb..4437a2d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -65,6 +65,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.view.HapticFeedbackConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -439,13 +440,13 @@
@Override // Binder call
public void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
- boolean always, String reason, boolean fromIme) {
+ String reason, int flags, int privFlags) {
// Note that the `performHapticFeedback` method does not take a token argument from the
// caller, and instead, uses this service as the token. This is to mitigate performance
// impact that would otherwise be caused due to marshal latency. Haptic feedback effects are
// short-lived, so we don't need to cancel when the process dies.
- performHapticFeedbackInternal(
- uid, deviceId, opPkg, constant, always, reason, /* token= */ this, fromIme);
+ performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+ this, flags, privFlags);
}
/**
@@ -456,8 +457,8 @@
@VisibleForTesting
@Nullable
HalVibration performHapticFeedbackInternal(
- int uid, int deviceId, String opPkg, int constant, boolean always, String reason,
- IBinder token, boolean fromIme) {
+ int uid, int deviceId, String opPkg, int constant, String reason,
+ IBinder token, int flags, int privFlags) {
HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
if (hapticVibrationProvider == null) {
Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
@@ -474,9 +475,8 @@
return null;
}
CombinedVibration vib = CombinedVibration.createParallel(effect);
- VibrationAttributes attrs =
- hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
- constant, /* bypassVibrationIntensitySetting= */ always, fromIme);
+ VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+ constant, flags, privFlags);
reason = "performHapticFeedback(constant=" + constant + "): " + reason;
VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
@@ -2295,10 +2295,11 @@
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
+ int flags = commonOptions.force
+ ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0;
HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant,
- /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
- deathBinder, false /* fromIme */);
+ /* reason= */ commonOptions.description, deathBinder, flags, /* privFlags */ 0);
maybeWaitOnVibration(vib, commonOptions);
return 0;
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 50ac801..66653ca 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -164,17 +164,16 @@
private void calculateAndCentreInitialBounds(Task task,
LaunchParamsController.LaunchParams outParams) {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- final Rect stableBounds = new Rect();
- task.getDisplayArea().getStableRect(stableBounds);
+ final Rect screenBounds = task.getDisplayArea().getBounds();
// The desired dimensions that a fully resizable window should take when initially entering
// desktop mode. Calculated as a percentage of the available display area as defined by the
// DESKTOP_MODE_INITIAL_BOUNDS_SCALE.
- final int desiredWidth = (int) (stableBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
- final int desiredHeight = (int) (stableBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredWidth = (int) (screenBounds.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight = (int) (screenBounds.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
outParams.mBounds.right = desiredWidth;
outParams.mBounds.bottom = desiredHeight;
- outParams.mBounds.offset(stableBounds.centerX() - outParams.mBounds.centerX(),
- stableBounds.centerY() - outParams.mBounds.centerY());
+ outParams.mBounds.offset(screenBounds.centerX() - outParams.mBounds.centerX(),
+ screenBounds.centerY() - outParams.mBounds.centerY());
}
private void initLogBuilder(Task task, ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index bf99ccd..d79c11ce 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -408,9 +408,8 @@
final boolean intersectsTopCutout = topDisplayCutout.intersects(
width - (windowWidth / 2), 0,
width + (windowWidth / 2), topDisplayCutout.bottom);
- if (mClingWindow != null &&
- (windowWidth < 0 || (width > 0 && intersectsTopCutout))) {
- final View iconView = mClingWindow.findViewById(R.id.immersive_cling_icon);
+ if (windowWidth < 0 || (width > 0 && intersectsTopCutout)) {
+ final View iconView = findViewById(R.id.immersive_cling_icon);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)
iconView.getLayoutParams();
lp.topMargin = topDisplayCutout.bottom;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f5108f5b..c26684f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,19 +324,19 @@
}
@Override
- public boolean performHapticFeedback(int effectId, boolean always, boolean fromIme) {
+ public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
final long ident = Binder.clearCallingIdentity();
try {
- return mService.mPolicy.performHapticFeedback(mUid, mPackageName,
- effectId, always, null, fromIme);
+ return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
+ privFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
- public void performHapticFeedbackAsync(int effectId, boolean always, boolean fromIme) {
- performHapticFeedback(effectId, always, fromIme);
+ public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
+ performHapticFeedback(effectId, flags, privFlags);
}
/* Drag/drop */
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5336044..f6a68d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -1897,7 +1898,8 @@
}
}
- private void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
+ @VisibleForTesting
+ void overrideAnimationOptionsToInfoIfNecessary(@NonNull TransitionInfo info) {
if (mOverrideOptions == null) {
return;
}
@@ -1914,12 +1916,28 @@
changes.get(i).setAnimationOptions(mOverrideOptions);
// TODO(b/295805497): Extract mBackgroundColor from AnimationOptions.
changes.get(i).setBackgroundColor(mOverrideOptions.getBackgroundColor());
+ } else if (shouldApplyAnimOptionsToEmbeddedTf(container.asTaskFragment())) {
+ // We only override AnimationOptions because backgroundColor should be from
+ // TaskFragmentAnimationParams.
+ changes.get(i).setAnimationOptions(mOverrideOptions);
}
}
}
updateActivityTargetForCrossProfileAnimation(info);
}
+ private boolean shouldApplyAnimOptionsToEmbeddedTf(@Nullable TaskFragment taskFragment) {
+ if (taskFragment == null || !taskFragment.isEmbedded()) {
+ return false;
+ }
+ if (taskFragment.getAnimationParams().hasOverrideAnimation()) {
+ // Always respect animation overrides from TaskFragmentAnimationParams.
+ return false;
+ }
+ // ActivityEmbedding animation adapter only support custom animation
+ return mOverrideOptions != null && mOverrideOptions.getType() == ANIM_CUSTOM;
+ }
+
/**
* Updates activity open target if {@link #mOverrideOptions} is
* {@link ANIM_OPEN_CROSS_PROFILE_APPS}.
@@ -1929,8 +1947,7 @@
return;
}
for (int i = 0; i < mTargets.size(); ++i) {
- final ActivityRecord activity = mTargets.get(i).mContainer
- .asActivityRecord();
+ final ActivityRecord activity = mTargets.get(i).mContainer.asActivityRecord();
final TransitionInfo.Change change = info.getChanges().get(i);
if (activity == null || change.getMode() != TRANSIT_OPEN) {
continue;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e122fe0..032d6b5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1131,9 +1131,9 @@
}
@Override
- public void onUserUnlocked(@NonNull TargetUser user) {
- if (user.isPreCreated()) return;
- mService.handleOnUserUnlocked(user.getUserIdentifier());
+ public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+ if (to.isPreCreated()) return;
+ mService.handleOnUserSwitching(from.getUserIdentifier(), to.getUserIdentifier());
}
}
@@ -3831,8 +3831,8 @@
mDevicePolicyEngine.handleUnlockUser(userId);
}
- void handleOnUserUnlocked(int userId) {
- showNewUserDisclaimerIfNecessary(userId);
+ void handleOnUserSwitching(int fromUserId, int toUserId) {
+ showNewUserDisclaimerIfNecessary(toUserId);
}
void handleStopUser(int userId) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 81fb1a0..d59f28b 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -98,8 +98,8 @@
assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
}
- private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
- final var collected = new ArrayList<UserDataRepository.UserData>();
+ private List<UserData> collectUserData(UserDataRepository repository) {
+ final var collected = new ArrayList<UserData>();
repository.forAllUserData(userData -> collected.add(userData));
return collected;
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 7138306..dec4634 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1046,6 +1046,77 @@
}
@Test
+ public void testWriteReadBaseRevisionCode() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ final int revisionCode = 311;
+ packageSetting.setBaseRevisionCode(revisionCode);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getBaseRevisionCode(), is(revisionCode));
+ }
+
+ @Test
+ public void testHasPkg_writeReadSplitVersions() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ final String splitOne = "one";
+ final String splitTwo = "two";
+ final int revisionOne = 311;
+ final int revisionTwo = 330;
+ packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+ packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getSplitNames().length, is(0));
+ assertThat(resultSetting.getSplitRevisionCodes().length, is(0));
+ }
+
+ @Test
+ public void testNoPkg_writeReadSplitVersions() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String splitOne = "one";
+ final String splitTwo = "two";
+ final int revisionOne = 311;
+ final int revisionTwo = 330;
+ packageSetting.setSplitNames(new String[] { splitOne, splitTwo});
+ packageSetting.setSplitRevisionCodes(new int[] { revisionOne, revisionTwo});
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getSplitNames()[0], is(splitOne));
+ assertThat(resultSetting.getSplitNames()[1], is(splitTwo));
+ assertThat(resultSetting.getSplitRevisionCodes()[0], is(revisionOne));
+ assertThat(resultSetting.getSplitRevisionCodes()[1], is(revisionTwo));
+ }
+
+ @Test
public void testWriteReadArchiveState() {
Settings settings = makeSettings();
PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 753db12..b9e99dd 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -36,6 +36,8 @@
"-Werror",
],
static_libs: [
+ "a11ychecker-protos-java-proto-lite",
+ "aatf",
"cts-input-lib",
"frameworks-base-testutils",
"services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
new file mode 100644
index 0000000..90d4275
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_VERSION_CODE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_VERSION_CODE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
+import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck;
+import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityCheckerUtilsTest {
+
+ PackageManager mMockPackageManager;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ mMockPackageManager = getMockPackageManagerWithInstalledApps();
+ }
+
+ @Test
+ public void processResults_happyPath_setsAllFields() {
+ AccessibilityNodeInfo mockNodeInfo =
+ new MockAccessibilityNodeInfoBuilder()
+ .setViewIdResourceName("TargetNode")
+ .build();
+ AccessibilityHierarchyCheckResult result1 =
+ new AccessibilityHierarchyCheckResult(
+ SpeakableTextPresentCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+ null);
+ AccessibilityHierarchyCheckResult result2 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+ AccessibilityHierarchyCheckResult result3 =
+ new AccessibilityHierarchyCheckResult(
+ ClassNameCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.INFO, null, 5, null);
+ AccessibilityHierarchyCheckResult result4 =
+ new AccessibilityHierarchyCheckResult(
+ ClickableSpanCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
+ null);
+
+ Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+ AccessibilityCheckerUtils.processResults(
+ mockNodeInfo,
+ List.of(result1, result2, result3, result4),
+ null,
+ mMockPackageManager,
+ new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME));
+
+ assertThat(atoms).containsExactly(
+ createAtom(A11yCheckerProto.AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK,
+ A11yCheckerProto.AccessibilityCheckResultType.WARNING, 1),
+ createAtom(A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK,
+ A11yCheckerProto.AccessibilityCheckResultType.ERROR, 2)
+ );
+ }
+
+ @Test
+ public void processResults_packageNameNotFound_returnsEmptySet()
+ throws PackageManager.NameNotFoundException {
+ when(mMockPackageManager.getPackageInfo("com.uninstalled.app", 0))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ AccessibilityNodeInfo mockNodeInfo =
+ new MockAccessibilityNodeInfoBuilder()
+ .setPackageName("com.uninstalled.app")
+ .setViewIdResourceName("TargetNode")
+ .build();
+ AccessibilityHierarchyCheckResult result1 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1,
+ null);
+ AccessibilityHierarchyCheckResult result2 =
+ new AccessibilityHierarchyCheckResult(
+ TouchTargetSizeCheck.class,
+ AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
+
+ Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms =
+ AccessibilityCheckerUtils.processResults(
+ mockNodeInfo,
+ List.of(result1, result2),
+ null,
+ mMockPackageManager,
+ new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME));
+
+ assertThat(atoms).isEmpty();
+ }
+
+ @Test
+ public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
+ AccessibilityEvent accessibilityEvent =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ accessibilityEvent.setPackageName(TEST_APP_PACKAGE_NAME);
+ accessibilityEvent.setClassName(TEST_ACTIVITY_NAME);
+
+ assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+ accessibilityEvent)).isEqualTo("MainActivity");
+ }
+
+ // Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
+ // latest prod preset.
+ @Test
+ public void checkClassToEnumMap_hasAllLatestPreset() {
+ ImmutableSet<AccessibilityHierarchyCheck> checkPreset =
+ AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
+ AccessibilityCheckPreset.LATEST);
+ Set<Class<? extends AccessibilityHierarchyCheck>> latestCheckClasses =
+ checkPreset.stream().map(AccessibilityHierarchyCheck::getClass).collect(
+ Collectors.toUnmodifiableSet());
+
+ assertThat(AccessibilityCheckerUtils.CHECK_CLASS_TO_ENUM_MAP.keySet())
+ .containsExactlyElementsIn(latestCheckClasses);
+ }
+
+
+ private static A11yCheckerProto.AccessibilityCheckResultReported createAtom(
+ A11yCheckerProto.AccessibilityCheckClass checkClass,
+ A11yCheckerProto.AccessibilityCheckResultType resultType,
+ int resultId) {
+ return A11yCheckerProto.AccessibilityCheckResultReported.newBuilder()
+ .setPackageName(TEST_APP_PACKAGE_NAME)
+ .setAppVersionCode(TEST_APP_VERSION_CODE)
+ .setUiElementPath(TEST_APP_PACKAGE_NAME + ":TargetNode")
+ .setWindowTitle(TEST_WINDOW_TITLE)
+ .setActivityName("")
+ .setSourceComponentName(new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_CLASS_NAME).flattenToString())
+ .setSourceVersionCode(TEST_A11Y_SERVICE_SOURCE_VERSION_CODE)
+ .setResultCheckClass(checkClass)
+ .setResultType(resultType)
+ .setResultId(resultId)
+ .build();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
similarity index 80%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
index 438277b..a53f42e 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
-import static android.view.accessibility.a11ychecker.MockAccessibilityNodeInfoBuilder.PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
@@ -36,7 +36,7 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityNodePathBuilderTest {
- public static final String RESOURCE_ID_PREFIX = PACKAGE_NAME + ":id/";
+ public static final String RESOURCE_ID_PREFIX = TEST_APP_PACKAGE_NAME + ":id/";
@Test
public void createNodePath_pathWithResourceNames() {
@@ -55,11 +55,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child))
- .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(root))
- .isEqualTo(PACKAGE_NAME + ":root_node");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node");
}
@Test
@@ -81,11 +81,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(root))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
}
@Test
@@ -105,11 +105,11 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/child1[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/child1[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout/TextView[2]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/TextView[2]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":FrameLayout");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout");
}
@Test
@@ -133,13 +133,13 @@
.build();
assertThat(AccessibilityNodePathBuilder.createNodePath(child1))
- .isEqualTo(PACKAGE_NAME + ":parentId/childId[1]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childId[1]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child2))
- .isEqualTo(PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]");
assertThat(AccessibilityNodePathBuilder.createNodePath(child3))
- .isEqualTo(PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]");
assertThat(AccessibilityNodePathBuilder.createNodePath(parent))
- .isEqualTo(PACKAGE_NAME + ":parentId");
+ .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId");
}
}
diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
similarity index 72%
rename from core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
rename to services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
index e363f0c..7cd3535 100644
--- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java
@@ -14,21 +14,33 @@
* limitations under the License.
*/
-package android.view.accessibility.a11ychecker;
+package com.android.server.accessibility.a11ychecker;
+
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import java.util.List;
final class MockAccessibilityNodeInfoBuilder {
- static final String PACKAGE_NAME = "com.example.app";
private final AccessibilityNodeInfo mMockNodeInfo = mock(AccessibilityNodeInfo.class);
MockAccessibilityNodeInfoBuilder() {
- when(mMockNodeInfo.getPackageName()).thenReturn(PACKAGE_NAME);
+ setPackageName(TEST_APP_PACKAGE_NAME);
+
+ AccessibilityWindowInfo windowInfo = new AccessibilityWindowInfo();
+ windowInfo.setTitle(TEST_WINDOW_TITLE);
+ when(mMockNodeInfo.getWindow()).thenReturn(windowInfo);
+ }
+
+ MockAccessibilityNodeInfoBuilder setPackageName(String packageName) {
+ when(mMockNodeInfo.getPackageName()).thenReturn(packageName);
+ return this;
}
MockAccessibilityNodeInfoBuilder setClassName(String className) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
new file mode 100644
index 0000000..7bdc029
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS
@@ -0,0 +1 @@
+include /services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
new file mode 100644
index 0000000..a04bbee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.a11ychecker;
+
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import org.mockito.Mockito;
+
+public class TestUtils {
+ static final String TEST_APP_PACKAGE_NAME = "com.example.app";
+ static final int TEST_APP_VERSION_CODE = 12321;
+ static final String TEST_ACTIVITY_NAME = "com.example.app.MainActivity";
+ static final String TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME = "com.assistive.app";
+ static final String TEST_A11Y_SERVICE_CLASS_NAME = "MyA11yService";
+ static final int TEST_A11Y_SERVICE_SOURCE_VERSION_CODE = 333555;
+ static final String TEST_WINDOW_TITLE = "Example window";
+
+ static PackageManager getMockPackageManagerWithInstalledApps()
+ throws PackageManager.NameNotFoundException {
+ PackageManager mockPackageManager = Mockito.mock(PackageManager.class);
+ ActivityInfo testActivityInfo = getTestActivityInfo();
+ ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
+ TEST_ACTIVITY_NAME);
+
+ when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+ .thenReturn(testActivityInfo);
+ when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
+ .thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
+ testActivityInfo));
+ when(mockPackageManager.getPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, 0))
+ .thenReturn(createPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
+ TEST_A11Y_SERVICE_SOURCE_VERSION_CODE, null));
+ return mockPackageManager;
+ }
+
+ static ActivityInfo getTestActivityInfo() {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = TEST_APP_PACKAGE_NAME;
+ activityInfo.name = TEST_ACTIVITY_NAME;
+ return activityInfo;
+ }
+
+ static PackageInfo createPackageInfo(String packageName, int versionCode,
+ @Nullable ActivityInfo activityInfo) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.setLongVersionCode(versionCode);
+ if (activityInfo != null) {
+ packageInfo.activities = new ActivityInfo[]{activityInfo};
+ }
+ return packageInfo;
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index ef9580c..758c84a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class), r -> r.run()) {
+ mock(AudioServerPermissionProvider.class)) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 4645156..2cb02bd 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class), r -> r.run()) {
+ mock(AudioServerPermissionProvider.class)) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index b7100ea..037c3c0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 746645a..27b552f 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class), r -> r.run());
+ mock(AudioServerPermissionProvider.class));
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index e45ab31..8e34ee1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
@NonNull PermissionEnforcer enforcer,
AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
+ audioPolicy, looper, appOps, enforcer, permissionProvider);
}
public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 633a3c9..901c036 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -54,6 +54,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
+import android.view.HapticFeedbackConstants;
import androidx.test.InstrumentationRegistry;
@@ -292,7 +293,7 @@
for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, /* fromIme= */ false);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
}
}
@@ -302,8 +303,7 @@
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false,
- false /* fromIme*/);
+ SAFE_MODE_ENABLED, /* flags */ 0, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
}
@@ -313,7 +313,8 @@
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true, false /* fromIme*/);
+ SAFE_MODE_ENABLED,
+ /* flags */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING, /* privFlags */ 0);
assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue();
}
@@ -325,7 +326,7 @@
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
}
@@ -338,7 +339,7 @@
for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
}
@@ -351,7 +352,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
@@ -366,7 +368,7 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -381,7 +383,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
.that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
@@ -398,7 +401,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -414,7 +418,7 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, false /* fromIme*/);
+ effectId, /* flags */ 0, /* privFlags */ 0);
assertWithMessage("Expected no FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isFalse();
@@ -430,7 +434,8 @@
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* bypassVibrationIntensitySetting= */ false, true /* fromIme*/);
+ effectId, /* flags */ 0,
+ HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
assertWithMessage("Expected FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE for effect "
+ effectId)
.that(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)).isTrue();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1875284..ef944db 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2661,9 +2661,10 @@
private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
int constant, boolean always) throws InterruptedException {
- HalVibration vib =
- service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
- constant, always, "some reason", service, false /* fromIme */);
+ HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
+ PACKAGE_NAME, constant, "some reason", service,
+ always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
+ 0 /* privFlags */);
if (vib != null) {
vib.waitForEnd();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index a4bec64..ad3fba3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -166,9 +166,9 @@
ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
final int desiredWidth =
- (int) (DISPLAY_STABLE_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
final int desiredHeight =
- (int) (DISPLAY_STABLE_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
assertEquals(desiredWidth, mResult.mBounds.width());
assertEquals(desiredHeight, mResult.mBounds.height());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 38ad9a7..4b0668f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,8 +270,8 @@
}
@Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId,
- boolean always, String reason, boolean fromIme) {
+ public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
+ int flags, int privFlags) {
return false;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 95d37eb..7d01b79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -35,6 +35,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_CONFIG_AT_END;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
@@ -77,13 +78,14 @@
import android.app.ActivityManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
@@ -95,6 +97,7 @@
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.SystemPerformanceHinter;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
@@ -104,7 +107,6 @@
import com.android.internal.graphics.ColorUtils;
import com.android.window.flags.Flags;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -120,16 +122,15 @@
* Build/Install/Run:
* atest WmTests:TransitionTests
*/
-@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class TransitionTests extends WindowTestsBase {
final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
private BLASTSyncEngine mSyncEngine;
+ private Transition mTransition;
+ private TransitionInfo mInfo;
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
private Transition createTestTransition(int transitType, TransitionController controller) {
final Transition transition = new Transition(transitType, 0 /* flags */, controller,
controller.mSyncEngine);
@@ -1994,6 +1995,221 @@
assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
}
+ @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCommonAnimOptions("testPackage");
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertEquals(options, mInfo.getAnimationOptions());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_nonCustomAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCommonAnimOptions("testPackage");
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+ embeddedTfChange.getAnimationOptions());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCrossProfileAnimOptions();
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+ activityChange.setMode(TRANSIT_OPEN);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertNull("Embedded TF change's AnimationOptions must not be overridden.",
+ embeddedTfChange.getAnimationOptions());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertTrue(activityChange.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL));
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, false /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ options, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must not be overridden.",
+ 0, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
+ initializeOverrideAnimationOptionsTest();
+
+ final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
+ embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.RED)
+ .setOpenAnimationResId(0x12345678)
+ .build());
+
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, false /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ final int expectedColor = embeddedTf.getAnimationParams().getAnimationBackgroundColor();
+ embeddedTfChange.setBackgroundColor(expectedColor);
+ final TransitionInfo.AnimationOptions expectedOptions = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", 0x12345678,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ 0, false /* overrideTaskTransition */);
+ embeddedTfChange.setAnimationOptions(expectedOptions);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertNull("Task change's AnimationOptions must not be overridden.",
+ taskChange.getAnimationOptions());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ expectedOptions, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must not be overridden.",
+ expectedColor, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
+ @Test
+ public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
+ initializeOverrideAnimationOptionsTest();
+ TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
+ .makeCustomAnimOptions("testPackage", Resources.ID_NULL,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
+ Color.GREEN, true /* overrideTaskTransition */);
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
+ null /* finishCallback */);
+
+ mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
+
+ final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
+ final TransitionInfo.Change taskChange = mInfo.getChanges().get(1);
+ final TransitionInfo.Change embeddedTfChange = mInfo.getChanges().get(2);
+ final TransitionInfo.Change activityChange = mInfo.getChanges().get(3);
+
+ assertNull("Display change's AnimationOptions must not be overridden.",
+ displayChange.getAnimationOptions());
+ assertEquals("Task change's AnimationOptions must be overridden.",
+ options, taskChange.getAnimationOptions());
+ assertEquals("Task change's background color must be overridden.",
+ options.getBackgroundColor(), taskChange.getBackgroundColor());
+ assertEquals("Embedded TF change's AnimationOptions must be overridden.",
+ options, embeddedTfChange.getAnimationOptions());
+ assertEquals("Embedded TF change's background color must be overridden.",
+ 0, embeddedTfChange.getBackgroundColor());
+ assertEquals("Activity change's AnimationOptions must be overridden.",
+ options, activityChange.getAnimationOptions());
+ assertEquals("Activity change's background color must be overridden.",
+ options.getBackgroundColor(), activityChange.getBackgroundColor());
+ }
+
+ private void initializeOverrideAnimationOptionsTest() {
+ mTransition = createTestTransition(TRANSIT_OPEN);
+
+ // Test set AnimationOptions for Activity and Task.
+ final Task task = createTask(mDisplayContent);
+ // Create an embedded TaskFragment.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ registerTaskFragmentOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ mWm.mCurrentUserId = nonEmbeddedActivity.mUserId;
+
+ mTransition.mTargets = new ArrayList<>();
+ mTransition.mTargets.add(new Transition.ChangeInfo(mDisplayContent));
+ mTransition.mTargets.add(new Transition.ChangeInfo(task));
+ mTransition.mTargets.add(new Transition.ChangeInfo(embeddedTf));
+ mTransition.mTargets.add(new Transition.ChangeInfo(nonEmbeddedActivity));
+
+ mInfo = new TransitionInfo(TRANSIT_OPEN, 0 /* flags */);
+ mInfo.addChange(new TransitionInfo.Change(mDisplayContent.mRemoteToken
+ .toWindowContainerToken(), mDisplayContent.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(task.mRemoteToken.toWindowContainerToken(),
+ task.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(embeddedTf.mRemoteToken.toWindowContainerToken(),
+ embeddedTf.getAnimationLeash()));
+ mInfo.addChange(new TransitionInfo.Change(null /* container */,
+ nonEmbeddedActivity.getAnimationLeash()));
+ }
+
@Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index aebae4e..4b83b65 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1078,6 +1078,11 @@
* @hide
*/
public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
+ /**
+ * Datagram type indicating that the message to be sent or received is of type SMS.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_SMS = 6;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
@@ -1086,7 +1091,8 @@
DATAGRAM_TYPE_LOCATION_SHARING,
DATAGRAM_TYPE_KEEP_ALIVE,
DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
- DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED,
+ DATAGRAM_TYPE_SMS
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}