Merge "Add device logger for active device and action state change" into main
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 10ce3b8..774ba74 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1565,6 +1565,7 @@
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
+ glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != nullptr) {
@@ -1641,8 +1642,10 @@
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
- glGenTextures(1, &frame.tid);
- glBindTexture(GL_TEXTURE_2D, frame.tid);
+ if (part.count != 1) {
+ glGenTextures(1, &frame.tid);
+ glBindTexture(GL_TEXTURE_2D, frame.tid);
+ }
int w, h;
// Set decoding option to alpha unpremultiplied so that the R, G, B channels
// of transparent pixels are preserved.
diff --git a/core/api/current.txt b/core/api/current.txt
index 9ba84c9..a3d8978 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15651,6 +15651,7 @@
public final class Gainmap implements android.os.Parcelable {
ctor public Gainmap(@NonNull android.graphics.Bitmap);
+ ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
method public int describeContents();
method @NonNull public float getDisplayRatioForFullHdr();
method @NonNull public float[] getEpsilonHdr();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7d2ef4d..6cad578 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1628,6 +1628,14 @@
*/
public static final int GROUP_ALERT_CHILDREN = 2;
+ /**
+ * Constant for the {@link Builder#setGroup(String) group key} that is added to notifications
+ * that are not already grouped when {@link Builder#setSilent()} is used.
+ *
+ * @hide
+ */
+ public static final String GROUP_KEY_SILENT = "silent";
+
private int mGroupAlertBehavior = GROUP_ALERT_ALL;
/**
@@ -4290,6 +4298,35 @@
}
/**
+ * If {@code true}, silences this instance of the notification, regardless of the sounds or
+ * vibrations set on the notification or notification channel. If {@code false}, then the
+ * normal sound and vibration logic applies.
+ *
+ * @hide
+ */
+ public @NonNull Builder setSilent(boolean silent) {
+ if (!silent) {
+ return this;
+ }
+ if (mN.isGroupSummary()) {
+ setGroupAlertBehavior(GROUP_ALERT_CHILDREN);
+ } else {
+ setGroupAlertBehavior(GROUP_ALERT_SUMMARY);
+ }
+
+ setVibrate(null);
+ setSound(null);
+ mN.defaults &= ~DEFAULT_SOUND;
+ mN.defaults &= ~DEFAULT_VIBRATE;
+ setDefaults(mN.defaults);
+
+ if (TextUtils.isEmpty(mN.mGroupKey)) {
+ setGroup(GROUP_KEY_SILENT);
+ }
+ return this;
+ }
+
+ /**
* Set the first line of text in the platform notification template.
*/
@NonNull
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index ee7836f..ed8484f 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -21,6 +21,7 @@
import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -46,7 +47,7 @@
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
IVirtualDevice createVirtualDevice(
- in IBinder token, String packageName, int associationId,
+ in IBinder token, in AttributionSource attributionSource, int associationId,
in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener,
in IVirtualDeviceSoundEffectListener soundEffectListener);
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index f68cfff..d13bfd4 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -145,7 +145,7 @@
mContext = context.getApplicationContext();
mVirtualDevice = service.createVirtualDevice(
new Binder(),
- mContext.getPackageName(),
+ mContext.getAttributionSource(),
associationId,
params,
mActivityListenerBinder,
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 45d6dc6..b6d8375 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -43,6 +43,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
+import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -501,6 +502,26 @@
+ ")";
}
+ /**
+ * Dumps debugging information about the VirtualDeviceParams
+ * @hide
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mName=" + mName);
+ pw.println(prefix + "mLockState=" + mLockState);
+ pw.println(prefix + "mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts);
+ pw.println(prefix + "mAllowedCrossTaskNavigations=" + mAllowedCrossTaskNavigations);
+ pw.println(prefix + "mBlockedCrossTaskNavigations=" + mBlockedCrossTaskNavigations);
+ pw.println(prefix + "mAllowedActivities=" + mAllowedActivities);
+ pw.println(prefix + "mBlockedActivities=" + mBlockedActivities);
+ pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
+ pw.println(prefix + "mDefaultNavigationPolicy=" + mDefaultNavigationPolicy);
+ pw.println(prefix + "mDefaultActivityPolicy=" + mDefaultActivityPolicy);
+ pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
+ pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
+ pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
+ }
+
@NonNull
public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
new Parcelable.Creator<VirtualDeviceParams>() {
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 3bdf9aa..0dbe411 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -104,6 +104,11 @@
parcel.writeInt(mFlags);
}
+ @Override
+ public String toString() {
+ return "VirtualSensorConfig{" + "mType=" + mType + ", mName='" + mName + '\'' + '}';
+ }
+
/**
* Returns the type of the sensor.
*
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index c3df17d..529363f 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -344,15 +344,22 @@
// If there is a label for the launcher intent, then use that as it is typically shorter.
// Otherwise, just use the top-level application name.
Intent launchIntent = pm.getLaunchIntentForPackage(mContext.getPackageName());
+ if (launchIntent == null) {
+ return getDefaultCallingApplicationLabel();
+ }
List<ResolveInfo> infos =
pm.queryIntentActivities(
launchIntent, PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY));
if (infos.size() > 0) {
return infos.get(0).loadLabel(pm);
}
+ return getDefaultCallingApplicationLabel();
+ }
+
+ private CharSequence getDefaultCallingApplicationLabel() {
return mContext.getApplicationInfo()
.loadSafeLabel(
- pm,
+ mContext.getPackageManager(),
/* ellipsizeDip= */ 0,
TextUtils.SAFE_STRING_FLAG_SINGLE_LINE
| TextUtils.SAFE_STRING_FLAG_TRIM);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c5585af..ea0f5ff 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -304,6 +304,8 @@
boolean isPackageSuspendedForUser(String packageName, int userId);
+ boolean isPackageQuarantinedForUser(String packageName, int userId);
+
Bundle getSuspendedPackageAppExtras(String packageName, int userId);
/**
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c2a0062..eedb25b 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,7 +123,7 @@
* credential, display a picker when multiple credentials exist, etc.
* Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
@@ -209,9 +209,9 @@
*
* <p>This API doesn't invoke any UI. It only performs the preparation work so that you can
* later launch the remaining get-credential operation (involves UIs) through the {@link
- * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Context,
+ * #getCredential(Context, PrepareGetCredentialResponse.PendingGetCredentialHandle,
* CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
- * the {@link #getCredential(GetCredentialRequest, Context, CancellationSignal, Executor,
+ * the {@link #getCredential(Context, GetCredentialRequest, CancellationSignal, Executor,
* OutcomeReceiver)} API that executes the whole operation in one call.
*
* @param request the request specifying type(s) of credentials to get from the user
@@ -261,7 +261,7 @@
* storing the new credential, etc.
* Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 912e8df..af448f0 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -466,6 +466,19 @@
// LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
+ * Set if emergency call button should show, for example if biometrics are
+ * required to access the dialer app
+ * @param showEmergencyCallButton if true, shows emergency call button
+ * @return This builder.
+ * @hide
+ */
+ @NonNull
+ public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) {
+ mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
*
* @return An instance of {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index e275078..24cfd164 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -48,6 +48,7 @@
private boolean mAllowBackgroundAuthentication;
private boolean mIgnoreEnrollmentState;
private boolean mIsForLegacyFingerprintManager = false;
+ private boolean mShowEmergencyCallButton = false;
public PromptInfo() {
@@ -72,6 +73,7 @@
mAllowBackgroundAuthentication = in.readBoolean();
mIgnoreEnrollmentState = in.readBoolean();
mIsForLegacyFingerprintManager = in.readBoolean();
+ mShowEmergencyCallButton = in.readBoolean();
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -111,6 +113,7 @@
dest.writeBoolean(mAllowBackgroundAuthentication);
dest.writeBoolean(mIgnoreEnrollmentState);
dest.writeBoolean(mIsForLegacyFingerprintManager);
+ dest.writeBoolean(mShowEmergencyCallButton);
}
// LINT.IfChange
@@ -228,6 +231,10 @@
mAllowedSensorIds.add(sensorId);
}
+ public void setShowEmergencyCallButton(boolean showEmergencyCallButton) {
+ mShowEmergencyCallButton = showEmergencyCallButton;
+ }
+
// Getters
public CharSequence getTitle() {
@@ -309,4 +316,8 @@
public boolean isForLegacyFingerprintManager() {
return mIsForLegacyFingerprintManager;
}
+
+ public boolean isShowEmergencyCallButton() {
+ return mShowEmergencyCallButton;
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 6baf91d7..ea951a5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -236,9 +236,10 @@
private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
new CameraExtensionManagerGlobal();
private final Object mLock = new Object();
- private final int PROXY_SERVICE_DELAY_MS = 1000;
+ private final int PROXY_SERVICE_DELAY_MS = 2000;
private InitializerFuture mInitFuture = null;
private ServiceConnection mConnection = null;
+ private int mConnectionCount = 0;
private ICameraExtensionsProxyService mProxy = null;
private boolean mSupportsAdvancedExtensions = false;
@@ -249,6 +250,15 @@
return GLOBAL_CAMERA_MANAGER;
}
+ private void releaseProxyConnectionLocked(Context ctx) {
+ if (mConnection != null ) {
+ ctx.unbindService(mConnection);
+ mConnection = null;
+ mProxy = null;
+ mConnectionCount = 0;
+ }
+ }
+
private void connectToProxyLocked(Context ctx) {
if (mConnection == null) {
Intent intent = new Intent();
@@ -270,7 +280,6 @@
mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName component) {
- mInitFuture.setStatus(false);
mConnection = null;
mProxy = null;
}
@@ -348,23 +357,32 @@
public boolean registerClient(Context ctx, IBinder token) {
synchronized (mLock) {
+ boolean ret = false;
connectToProxyLocked(ctx);
if (mProxy == null) {
return false;
}
+ mConnectionCount++;
try {
- return mProxy.registerClient(token);
+ ret = mProxy.registerClient(token);
} catch (RemoteException e) {
Log.e(TAG, "Failed to initialize extension! Extension service does "
+ " not respond!");
}
+ if (!ret) {
+ mConnectionCount--;
+ }
- return false;
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
+
+ return ret;
}
}
- public void unregisterClient(IBinder token) {
+ public void unregisterClient(Context ctx, IBinder token) {
synchronized (mLock) {
if (mProxy != null) {
try {
@@ -372,6 +390,11 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to de-initialize extension! Extension service does"
+ " not respond!");
+ } finally {
+ mConnectionCount--;
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
}
}
}
@@ -446,8 +469,8 @@
/**
* @hide
*/
- public static void unregisterClient(IBinder token) {
- CameraExtensionManagerGlobal.get().unregisterClient(token);
+ public static void unregisterClient(Context ctx, IBinder token) {
+ CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
}
/**
@@ -578,7 +601,7 @@
}
}
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableList(ret);
@@ -626,7 +649,7 @@
Log.e(TAG, "Failed to query the extension for postview availability! Extension "
+ "service does not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return false;
@@ -722,7 +745,7 @@
+ "service does not respond!");
return Collections.emptyList();
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
}
@@ -791,7 +814,7 @@
+ " not respond!");
return new ArrayList<>();
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
}
@@ -872,7 +895,7 @@
}
}
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -957,7 +980,7 @@
Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return null;
@@ -998,7 +1021,7 @@
Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return false;
@@ -1075,7 +1098,7 @@
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture request keys!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
@@ -1155,7 +1178,7 @@
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture result keys!");
} finally {
- unregisterClient(token);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index e06699b..c7e74c0 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -90,7 +90,7 @@
private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
private RequestProcessor mRequestProcessor = new RequestProcessor();
private final int mSessionId;
- private final IBinder mToken;
+ private IBinder mToken = null;
private Surface mClientRepeatingRequestSurface;
private Surface mClientCaptureSurface;
@@ -103,6 +103,8 @@
private boolean mInitialized;
private boolean mSessionClosed;
+ private final Context mContext;
+
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock;
@@ -113,14 +115,9 @@
public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@NonNull Map<String, CameraCharacteristics> characteristicsMap,
- @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
+ @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId,
+ @NonNull IBinder token)
throws CameraAccessException, RemoteException {
- final IBinder token = new Binder(TAG + " : " + sessionId);
- boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
- if (!success) {
- throw new UnsupportedOperationException("Unsupported extension!");
- }
-
String cameraId = cameraDevice.getId();
CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx,
cameraId, characteristicsMap);
@@ -204,8 +201,9 @@
IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
config.getExtension());
extender.init(cameraId, characteristicsMapNative);
- CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
- cameraDevice, characteristicsMapNative, repeatingRequestSurface,
+
+ CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
+ extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
burstCaptureSurface, postviewSurface, config.getStateCallback(),
config.getExecutor(), sessionId, token);
@@ -217,13 +215,16 @@
return ret;
}
- private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
+ private CameraAdvancedExtensionSessionImpl(Context ctx,
+ @NonNull IAdvancedExtenderImpl extender,
@NonNull CameraDeviceImpl cameraDevice,
Map<String, CameraMetadataNative> characteristicsMap,
@Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
@Nullable Surface postviewSurface,
@NonNull StateCallback callback, @NonNull Executor executor,
- int sessionId, @NonNull IBinder token) {
+ int sessionId,
+ @NonNull IBinder token) {
+ mContext = ctx;
mAdvancedExtender = extender;
mCameraDevice = cameraDevice;
mCharacteristicsMap = characteristicsMap;
@@ -578,12 +579,16 @@
mSessionProcessor = null;
}
- CameraExtensionCharacteristics.unregisterClient(mToken);
- if (mInitialized || (mCaptureSession != null)) {
- notifyClose = true;
- CameraExtensionCharacteristics.releaseSession();
+
+ if (mToken != null) {
+ if (mInitialized || (mCaptureSession != null)) {
+ notifyClose = true;
+ CameraExtensionCharacteristics.releaseSession();
+ }
+ CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
}
mInitialized = false;
+ mToken = null;
for (ImageReader reader : mReaderMap.values()) {
reader.close();
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d3bde4b..181ab2c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2550,19 +2550,32 @@
HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>(
mPhysicalIdsToChars);
characteristicsMap.put(mCameraId, mCharacteristics);
+ boolean initializationFailed = true;
+ IBinder token = new Binder(TAG + " : " + mNextSessionId++);
try {
+ boolean ret = CameraExtensionCharacteristics.registerClient(mContext, token);
+ if (!ret) {
+ token = null;
+ throw new UnsupportedOperationException("Unsupported extension!");
+ }
+
if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) {
mCurrentAdvancedExtensionSession =
CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession(
this, characteristicsMap, mContext, extensionConfiguration,
- mNextSessionId++);
+ mNextSessionId, token);
} else {
mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession(
this, characteristicsMap, mContext, extensionConfiguration,
- mNextSessionId++);
+ mNextSessionId, token);
}
+ initializationFailed = false;
} catch (RemoteException e) {
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+ } finally {
+ if (initializationFailed && (token != null)) {
+ CameraExtensionCharacteristics.unregisterClient(mContext, token);
+ }
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 5d25681..bf77681 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -91,7 +91,7 @@
private final Set<CaptureRequest.Key> mSupportedRequestKeys;
private final Set<CaptureResult.Key> mSupportedResultKeys;
private final ExtensionSessionStatsAggregator mStatsAggregator;
- private final IBinder mToken;
+ private IBinder mToken = null;
private boolean mCaptureResultsSupported;
private CameraCaptureSession mCaptureSession = null;
@@ -119,6 +119,8 @@
// will do so internally.
private boolean mInternalRepeatingRequestEnabled = true;
+ private final Context mContext;
+
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock;
@@ -135,14 +137,9 @@
@NonNull Map<String, CameraCharacteristics> characteristicsMap,
@NonNull Context ctx,
@NonNull ExtensionSessionConfiguration config,
- int sessionId)
+ int sessionId,
+ @NonNull IBinder token)
throws CameraAccessException, RemoteException {
- final IBinder token = new Binder(TAG + " : " + sessionId);
- boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
- if (!success) {
- throw new UnsupportedOperationException("Unsupported extension!");
- }
-
String cameraId = cameraDevice.getId();
CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx,
cameraId, characteristicsMap);
@@ -234,6 +231,7 @@
characteristicsMap.get(cameraId).getNativeMetadata());
CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
+ ctx,
extenders.second,
extenders.first,
supportedPreviewSizes,
@@ -256,7 +254,7 @@
return session;
}
- public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
+ public CameraExtensionSessionImpl(Context ctx, @NonNull IImageCaptureExtenderImpl imageExtender,
@NonNull IPreviewExtenderImpl previewExtender,
@NonNull List<Size> previewSizes,
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@@ -269,6 +267,7 @@
@NonNull IBinder token,
@NonNull Set<CaptureRequest.Key> requestKeys,
@Nullable Set<CaptureResult.Key> resultKeys) {
+ mContext = ctx;
mImageExtender = imageExtender;
mPreviewExtender = previewExtender;
mCameraDevice = cameraDevice;
@@ -878,12 +877,15 @@
+ " respond!");
}
- CameraExtensionCharacteristics.unregisterClient(mToken);
- if (mInitialized || (mCaptureSession != null)) {
- notifyClose = true;
- CameraExtensionCharacteristics.releaseSession();
+ if (mToken != null) {
+ if (mInitialized || (mCaptureSession != null)) {
+ notifyClose = true;
+ CameraExtensionCharacteristics.releaseSession();
+ }
+ CameraExtensionCharacteristics.unregisterClient(mContext, mToken);
}
mInitialized = false;
+ mToken = null;
if (mRepeatingRequestImageCallback != null) {
mRepeatingRequestImageCallback.close();
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 21540bf..5e53373 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1193,12 +1193,12 @@
String physicalCameraId = source.readString();
boolean isMultiResolutionOutput = source.readInt() == 1;
int[] sensorPixelModesUsed = source.createIntArray();
- long streamUseCase = source.readLong();
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
long dynamicRangeProfile = source.readLong();
DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile);
int colorSpace = source.readInt();
+ long streamUseCase = source.readLong();
int timestampBase = source.readInt();
int mirrorMode = source.readInt();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 94bff89..4700720 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -370,8 +370,9 @@
/**
* Returns the default size of the surface associated with the display, or null if the surface
- * is not provided for layer mirroring by SurfaceFlinger.
- * Only used for mirroring started from MediaProjection.
+ * is not provided for layer mirroring by SurfaceFlinger. Size is rotated to reflect the current
+ * display device orientation.
+ * Used for mirroring from MediaProjection, or a physical display based on display flags.
*/
public abstract Point getDisplaySurfaceDefaultSize(int displayId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 6ef1dc0..8f7725ec 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -10,6 +10,7 @@
# BatteryStats
per-file *BatteryConsumer* = file:/BATTERY_STATS_OWNERS
per-file BatteryManager* = file:/BATTERY_STATS_OWNERS
+per-file PowerMonitor* = file:/BATTERY_STATS_OWNERS
per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS
per-file *Stats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index bf72b1d..2cda787 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,26 +18,19 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.hardware.vibrator.IVibrator;
+import android.os.vibrator.VibratorInfoFactory;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Range;
-import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Function;
/**
* Vibrator implementation that controls the main system vibrator.
@@ -82,7 +75,7 @@
if (vibratorIds.length == 0) {
// It is known that the device has no vibrator, so cache and return info that
// reflects the lack of support for effects/primitives.
- return mVibratorInfo = new NoVibratorInfo();
+ return mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
}
VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
for (int i = 0; i < vibratorIds.length; i++) {
@@ -96,12 +89,7 @@
}
vibratorInfos[i] = vibrator.getInfo();
}
- if (vibratorInfos.length == 1) {
- // Device has a single vibrator info, cache and return successfully loaded info.
- return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
- }
- // Device has multiple vibrators, generate a single info representing all of them.
- return mVibratorInfo = new MultiVibratorInfo(vibratorInfos);
+ return mVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, vibratorInfos);
}
}
@@ -275,296 +263,6 @@
}
/**
- * Represents a device with no vibrator as a single {@link VibratorInfo}.
- *
- * @hide
- */
- @VisibleForTesting
- public static class NoVibratorInfo extends VibratorInfo {
- public NoVibratorInfo() {
- // Use empty arrays to indicate no support, while null would indicate support unknown.
- super(/* id= */ -1,
- /* capabilities= */ 0,
- /* supportedEffects= */ new SparseBooleanArray(),
- /* supportedBraking= */ new SparseBooleanArray(),
- /* supportedPrimitives= */ new SparseIntArray(),
- /* primitiveDelayMax= */ 0,
- /* compositionSizeMax= */ 0,
- /* pwlePrimitiveDurationMax= */ 0,
- /* pwleSizeMax= */ 0,
- /* qFactor= */ Float.NaN,
- new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
- /* minFrequencyHz= */ Float.NaN,
- /* frequencyResolutionHz= */ Float.NaN,
- /* maxAmplitudes= */ null));
- }
- }
-
- /**
- * Represents multiple vibrator information as a single {@link VibratorInfo}.
- *
- * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
- * support.
- *
- * @hide
- */
- @VisibleForTesting
- public static class MultiVibratorInfo extends VibratorInfo {
- // Epsilon used for float comparison applied in calculations for the merged info.
- private static final float EPSILON = 1e-5f;
-
- public MultiVibratorInfo(VibratorInfo[] vibrators) {
- // Need to use an extra constructor to share the computation in super initialization.
- this(vibrators, frequencyProfileIntersection(vibrators));
- }
-
- private MultiVibratorInfo(VibratorInfo[] vibrators,
- VibratorInfo.FrequencyProfile mergedProfile) {
- super(/* id= */ -1,
- capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
- supportedEffectsIntersection(vibrators),
- supportedBrakingIntersection(vibrators),
- supportedPrimitivesAndDurationsIntersection(vibrators),
- integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
- integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
- integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
- integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
- floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
- mergedProfile);
- }
-
- private static int capabilitiesIntersection(VibratorInfo[] infos,
- boolean frequencyProfileIsEmpty) {
- int intersection = ~0;
- for (VibratorInfo info : infos) {
- intersection &= info.getCapabilities();
- }
- if (frequencyProfileIsEmpty) {
- // Revoke frequency control if the merged frequency profile ended up empty.
- intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
- }
- return intersection;
- }
-
- @Nullable
- private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
- for (VibratorInfo info : infos) {
- if (!info.isBrakingSupportKnown()) {
- // If one vibrator support is unknown, then the intersection is also unknown.
- return null;
- }
- }
-
- SparseBooleanArray intersection = new SparseBooleanArray();
- SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
-
- brakingIdLoop:
- for (int i = 0; i < firstVibratorBraking.size(); i++) {
- int brakingId = firstVibratorBraking.keyAt(i);
- if (!firstVibratorBraking.valueAt(i)) {
- // The first vibrator already doesn't support this braking, so skip it.
- continue brakingIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- if (!infos[j].hasBrakingSupport(brakingId)) {
- // One vibrator doesn't support this braking, so the intersection doesn't.
- continue brakingIdLoop;
- }
- }
-
- intersection.put(brakingId, true);
- }
-
- return intersection;
- }
-
- @Nullable
- private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
- for (VibratorInfo info : infos) {
- if (!info.isEffectSupportKnown()) {
- // If one vibrator support is unknown, then the intersection is also unknown.
- return null;
- }
- }
-
- SparseBooleanArray intersection = new SparseBooleanArray();
- SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
-
- effectIdLoop:
- for (int i = 0; i < firstVibratorEffects.size(); i++) {
- int effectId = firstVibratorEffects.keyAt(i);
- if (!firstVibratorEffects.valueAt(i)) {
- // The first vibrator already doesn't support this effect, so skip it.
- continue effectIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
- // One vibrator doesn't support this effect, so the intersection doesn't.
- continue effectIdLoop;
- }
- }
-
- intersection.put(effectId, true);
- }
-
- return intersection;
- }
-
- @NonNull
- private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
- VibratorInfo[] infos) {
- SparseIntArray intersection = new SparseIntArray();
- SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
-
- primitiveIdLoop:
- for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
- int primitiveId = firstVibratorPrimitives.keyAt(i);
- int primitiveDuration = firstVibratorPrimitives.valueAt(i);
- if (primitiveDuration == 0) {
- // The first vibrator already doesn't support this primitive, so skip it.
- continue primitiveIdLoop;
- }
-
- for (int j = 1; j < infos.length; j++) {
- int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
- if (vibratorPrimitiveDuration == 0) {
- // One vibrator doesn't support this primitive, so the intersection doesn't.
- continue primitiveIdLoop;
- } else {
- // The primitive vibration duration is the maximum among all vibrators.
- primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
- }
- }
-
- intersection.put(primitiveId, primitiveDuration);
- }
- return intersection;
- }
-
- private static int integerLimitIntersection(VibratorInfo[] infos,
- Function<VibratorInfo, Integer> propertyGetter) {
- int limit = 0; // Limit 0 means unlimited
- for (VibratorInfo info : infos) {
- int vibratorLimit = propertyGetter.apply(info);
- if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
- // This vibrator is limited and intersection is unlimited or has a larger limit:
- // use smaller limit here for the intersection.
- limit = vibratorLimit;
- }
- }
- return limit;
- }
-
- private static float floatPropertyIntersection(VibratorInfo[] infos,
- Function<VibratorInfo, Float> propertyGetter) {
- float property = propertyGetter.apply(infos[0]);
- if (Float.isNaN(property)) {
- // If one vibrator is undefined then the intersection is undefined.
- return Float.NaN;
- }
- for (int i = 1; i < infos.length; i++) {
- if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
- // If one vibrator has a different value then the intersection is undefined.
- return Float.NaN;
- }
- }
- return property;
- }
-
- @NonNull
- private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
- float freqResolution = floatPropertyIntersection(infos,
- info -> info.getFrequencyProfile().getFrequencyResolutionHz());
- float resonantFreq = floatPropertyIntersection(infos,
- VibratorInfo::getResonantFrequencyHz);
- Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
-
- if ((freqRange == null) || Float.isNaN(freqResolution)) {
- return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
- }
-
- int amplitudeCount =
- Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
- float[] maxAmplitudes = new float[amplitudeCount];
-
- // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
- // will fail if the loop below is broken and do not replace filled values with actual
- // vibrator measurements.
- Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
-
- for (VibratorInfo info : infos) {
- Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
- float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
- int vibratorStartIdx = Math.round(
- (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
- int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
-
- if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
- Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
- + " profiles: attempted to fetch from vibrator "
- + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
- return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
- }
-
- for (int i = 0; i < maxAmplitudes.length; i++) {
- maxAmplitudes[i] = Math.min(maxAmplitudes[i],
- vibratorMaxAmplitudes[vibratorStartIdx + i]);
- }
- }
-
- return new FrequencyProfile(resonantFreq, freqRange.getLower(),
- freqResolution, maxAmplitudes);
- }
-
- @Nullable
- private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
- float frequencyResolution) {
- Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
- if (firstRange == null) {
- // If one vibrator is undefined then the intersection is undefined.
- return null;
- }
- float intersectionLower = firstRange.getLower();
- float intersectionUpper = firstRange.getUpper();
-
- // Generate the intersection of all vibrator supported ranges, making sure that both
- // min supported frequencies are aligned w.r.t. the frequency resolution.
-
- for (int i = 1; i < infos.length; i++) {
- Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
- if (vibratorRange == null) {
- // If one vibrator is undefined then the intersection is undefined.
- return null;
- }
-
- if ((vibratorRange.getLower() >= intersectionUpper)
- || (vibratorRange.getUpper() <= intersectionLower)) {
- // If the range and intersection are disjoint then the intersection is undefined
- return null;
- }
-
- float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
- if ((frequencyDelta % frequencyResolution) > EPSILON) {
- // If the intersection is not aligned with one vibrator then it's undefined
- return null;
- }
-
- intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
- intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
- }
-
- if ((intersectionUpper - intersectionLower) < frequencyResolution) {
- // If the intersection is empty then it's undefined.
- return null;
- }
-
- return Range.create(intersectionLower, intersectionUpper);
- }
- }
-
- /**
* Listener for all vibrators state change.
*
* <p>This registers a listener to all vibrators to merge the callbacks into a single state
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index b24b45d..08b32bf 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -525,14 +525,14 @@
public abstract long getDuration();
/**
- * Checks if a given {@link Vibrator} can play this effect as intended.
+ * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended.
*
- * <p>See @link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information
- * about what counts as supported by a vibrator, and what counts as not.
+ * <p>See {@link VibratorInfo#areVibrationFeaturesSupported(VibrationEffect)} for more
+ * information about what counts as supported by a vibrator, and what counts as not.
*
* @hide
*/
- public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+ public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
/**
* Returns true if this effect could represent a touch haptic feedback.
@@ -813,9 +813,9 @@
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
for (VibrationEffectSegment segment : mSegments) {
- if (!segment.areVibrationFeaturesSupported(vibrator)) {
+ if (!segment.areVibrationFeaturesSupported(vibratorInfo)) {
return false;
}
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 4e852e3..79e0ca8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -216,9 +216,7 @@
*/
@TestApi
public boolean hasFrequencyControl() {
- // We currently can only control frequency of the vibration using the compose PWLE method.
- return getInfo().hasCapability(
- IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ return getInfo().hasFrequencyControl();
}
/**
@@ -240,7 +238,7 @@
* @hide
*/
public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
- return effect.areVibrationFeaturesSupported(this);
+ return getInfo().areVibrationFeaturesSupported(effect);
}
/**
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 02e6856..4f8c24d 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -156,6 +156,16 @@
return false;
}
VibratorInfo that = (VibratorInfo) o;
+ return mId == that.mId && equalContent(that);
+ }
+
+ /**
+ * Returns {@code true} only if the properties and capabilities of the provided info, except for
+ * the ID, equals to this info. Returns {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean equalContent(VibratorInfo that) {
int supportedPrimitivesCount = mSupportedPrimitives.size();
if (supportedPrimitivesCount != that.mSupportedPrimitives.size()) {
return false;
@@ -168,7 +178,7 @@
return false;
}
}
- return mId == that.mId && mCapabilities == that.mCapabilities
+ return mCapabilities == that.mCapabilities
&& mPrimitiveDelayMax == that.mPrimitiveDelayMax
&& mCompositionSizeMax == that.mCompositionSizeMax
&& mPwlePrimitiveDurationMax == that.mPwlePrimitiveDurationMax
@@ -242,6 +252,17 @@
}
/**
+ * Check whether the vibrator has frequency control.
+ *
+ * @return True if the hardware can control the frequency of the vibrations, otherwise false.
+ */
+ public boolean hasFrequencyControl() {
+ // We currently can only control frequency of the vibration using the compose PWLE method.
+ return hasCapability(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+
+ /**
* Returns a default value to be applied to composed PWLE effects for braking.
*
* @return a supported braking value, one of android.hardware.vibrator.Braking.*
@@ -323,6 +344,23 @@
}
/**
+ * Query whether or not the vibrator supports all components of a given {@link VibrationEffect}
+ * (i.e. the vibrator can play the given effect as intended).
+ *
+ * <p>See {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more
+ * information on how the vibrator support is determined.
+ *
+ * @param effect the {@link VibrationEffect} to check if it is supported
+ * @return {@code true} if the vibrator can play the given {@code effect} as intended,
+ * {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean areVibrationFeaturesSupported(@NonNull VibrationEffect effect) {
+ return effect.areVibrationFeaturesSupported(this);
+ }
+
+ /**
* Query the estimated duration of given primitive.
*
* @param primitiveId Which primitives to query for.
@@ -417,7 +455,8 @@
return mFrequencyProfile;
}
- protected long getCapabilities() {
+ /** Returns a single int representing all the capabilities of the vibrator. */
+ public long getCapabilities() {
return mCapabilities;
}
diff --git a/core/java/android/os/vibrator/MultiVibratorInfo.java b/core/java/android/os/vibrator/MultiVibratorInfo.java
new file mode 100644
index 0000000..5f32731
--- /dev/null
+++ b/core/java/android/os/vibrator/MultiVibratorInfo.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibrator;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.util.Range;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * Represents multiple vibrator information as a single {@link VibratorInfo}.
+ *
+ * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
+ * support.
+ *
+ * @hide
+ */
+public final class MultiVibratorInfo extends VibratorInfo {
+ private static final String TAG = "MultiVibratorInfo";
+
+ // Epsilon used for float comparison applied in calculations for the merged info.
+ private static final float EPSILON = 1e-5f;
+
+ public MultiVibratorInfo(int id, VibratorInfo[] vibrators) {
+ this(id, vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(
+ int id, VibratorInfo[] vibrators, VibratorInfo.FrequencyProfile mergedProfile) {
+ super(id,
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
+ supportedEffectsIntersection(vibrators),
+ supportedBrakingIntersection(vibrators),
+ supportedPrimitivesAndDurationsIntersection(vibrators),
+ integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
+ integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
+ floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+ mergedProfile);
+ }
+
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
+ int intersection = ~0;
+ for (VibratorInfo info : infos) {
+ intersection &= info.getCapabilities();
+ }
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isBrakingSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
+
+ brakingIdLoop:
+ for (int i = 0; i < firstVibratorBraking.size(); i++) {
+ int brakingId = firstVibratorBraking.keyAt(i);
+ if (!firstVibratorBraking.valueAt(i)) {
+ // The first vibrator already doesn't support this braking, so skip it.
+ continue brakingIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (!infos[j].hasBrakingSupport(brakingId)) {
+ // One vibrator doesn't support this braking, so the intersection doesn't.
+ continue brakingIdLoop;
+ }
+ }
+
+ intersection.put(brakingId, true);
+ }
+
+ return intersection;
+ }
+
+ @Nullable
+ private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
+ for (VibratorInfo info : infos) {
+ if (!info.isEffectSupportKnown()) {
+ // If one vibrator support is unknown, then the intersection is also unknown.
+ return null;
+ }
+ }
+
+ SparseBooleanArray intersection = new SparseBooleanArray();
+ SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
+
+ effectIdLoop:
+ for (int i = 0; i < firstVibratorEffects.size(); i++) {
+ int effectId = firstVibratorEffects.keyAt(i);
+ if (!firstVibratorEffects.valueAt(i)) {
+ // The first vibrator already doesn't support this effect, so skip it.
+ continue effectIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ if (infos[j].isEffectSupported(effectId) != Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+ // One vibrator doesn't support this effect, so the intersection doesn't.
+ continue effectIdLoop;
+ }
+ }
+
+ intersection.put(effectId, true);
+ }
+
+ return intersection;
+ }
+
+ @NonNull
+ private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+ VibratorInfo[] infos) {
+ SparseIntArray intersection = new SparseIntArray();
+ SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+ primitiveIdLoop:
+ for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+ int primitiveId = firstVibratorPrimitives.keyAt(i);
+ int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+ if (primitiveDuration == 0) {
+ // The first vibrator already doesn't support this primitive, so skip it.
+ continue primitiveIdLoop;
+ }
+
+ for (int j = 1; j < infos.length; j++) {
+ int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+ if (vibratorPrimitiveDuration == 0) {
+ // One vibrator doesn't support this primitive, so the intersection doesn't.
+ continue primitiveIdLoop;
+ } else {
+ // The primitive vibration duration is the maximum among all vibrators.
+ primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+ }
+ }
+
+ intersection.put(primitiveId, primitiveDuration);
+ }
+ return intersection;
+ }
+
+ private static int integerLimitIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Integer> propertyGetter) {
+ int limit = 0; // Limit 0 means unlimited
+ for (VibratorInfo info : infos) {
+ int vibratorLimit = propertyGetter.apply(info);
+ if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
+ // This vibrator is limited and intersection is unlimited or has a larger limit:
+ // use smaller limit here for the intersection.
+ limit = vibratorLimit;
+ }
+ }
+ return limit;
+ }
+
+ private static float floatPropertyIntersection(VibratorInfo[] infos,
+ Function<VibratorInfo, Float> propertyGetter) {
+ float property = propertyGetter.apply(infos[0]);
+ if (Float.isNaN(property)) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return Float.NaN;
+ }
+ for (int i = 1; i < infos.length; i++) {
+ if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
+ // If one vibrator has a different value then the intersection is undefined.
+ return Float.NaN;
+ }
+ }
+ return property;
+ }
+
+ @NonNull
+ private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+ float freqResolution = floatPropertyIntersection(infos,
+ info -> info.getFrequencyProfile().getFrequencyResolutionHz());
+ float resonantFreq = floatPropertyIntersection(infos,
+ VibratorInfo::getResonantFrequencyHz);
+ Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
+
+ if ((freqRange == null) || Float.isNaN(freqResolution)) {
+ return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
+ }
+
+ int amplitudeCount =
+ Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
+ float[] maxAmplitudes = new float[amplitudeCount];
+
+ // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
+ // will fail if the loop below is broken and do not replace filled values with actual
+ // vibrator measurements.
+ Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
+
+ for (VibratorInfo info : infos) {
+ Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
+ float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
+ int vibratorStartIdx = Math.round(
+ (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
+ int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
+
+ if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
+ Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+ + " profiles: attempted to fetch from vibrator "
+ + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
+ return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
+ }
+
+ for (int i = 0; i < maxAmplitudes.length; i++) {
+ maxAmplitudes[i] = Math.min(maxAmplitudes[i],
+ vibratorMaxAmplitudes[vibratorStartIdx + i]);
+ }
+ }
+
+ return new FrequencyProfile(resonantFreq, freqRange.getLower(),
+ freqResolution, maxAmplitudes);
+ }
+
+ @Nullable
+ private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
+ float frequencyResolution) {
+ Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
+ if (firstRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+ float intersectionLower = firstRange.getLower();
+ float intersectionUpper = firstRange.getUpper();
+
+ // Generate the intersection of all vibrator supported ranges, making sure that both
+ // min supported frequencies are aligned w.r.t. the frequency resolution.
+
+ for (int i = 1; i < infos.length; i++) {
+ Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
+ if (vibratorRange == null) {
+ // If one vibrator is undefined then the intersection is undefined.
+ return null;
+ }
+
+ if ((vibratorRange.getLower() >= intersectionUpper)
+ || (vibratorRange.getUpper() <= intersectionLower)) {
+ // If the range and intersection are disjoint then the intersection is undefined
+ return null;
+ }
+
+ float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
+ if ((frequencyDelta % frequencyResolution) > EPSILON) {
+ // If the intersection is not aligned with one vibrator then it's undefined
+ return null;
+ }
+
+ intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
+ intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
+ }
+
+ if ((intersectionUpper - intersectionLower) < frequencyResolution) {
+ // If the intersection is empty then it's undefined.
+ return null;
+ }
+
+ return Range.create(intersectionLower, intersectionUpper);
+ }
+}
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index 42b6c2da..a035092 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -23,6 +23,7 @@
import android.os.Parcelable;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorInfo;
import java.util.Objects;
@@ -77,8 +78,8 @@
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
- if (vibrator.areAllEffectsSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+ if (vibratorInfo.isEffectSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
return true;
}
if (!mFallback) {
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index c52a09c..95d97bf 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -22,7 +22,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -77,8 +77,8 @@
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
- return vibrator.areAllPrimitivesSupported(mPrimitiveId);
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
+ return vibratorInfo.isPrimitiveSupported(mPrimitiveId);
}
/** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index e997bcd..5f9d102 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -20,7 +20,7 @@
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -96,7 +96,7 @@
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
boolean areFeaturesSupported = true;
// If the start/end frequencies are not the same, require frequency control since we need to
// ramp up/down the frequency.
@@ -104,7 +104,7 @@
// If there is no frequency ramping, make sure that the one frequency used does not
// require frequency control.
|| frequencyRequiresFrequencyControl(mStartFrequencyHz)) {
- areFeaturesSupported &= vibrator.hasFrequencyControl();
+ areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
}
// If the start/end amplitudes are not the same, require amplitude control since we need to
// ramp up/down the amplitude.
@@ -112,7 +112,7 @@
// If there is no amplitude ramping, make sure that the amplitude used does not
// require amplitude control.
|| amplitudeRequiresAmplitudeControl(mStartAmplitude)) {
- areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
}
return areFeaturesSupported;
}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index a585aa8..9576a5b 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -21,7 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
import com.android.internal.util.Preconditions;
@@ -82,13 +82,13 @@
/** @hide */
@Override
- public boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator) {
+ public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) {
boolean areFeaturesSupported = true;
if (frequencyRequiresFrequencyControl(mFrequencyHz)) {
- areFeaturesSupported &= vibrator.hasFrequencyControl();
+ areFeaturesSupported &= vibratorInfo.hasFrequencyControl();
}
if (amplitudeRequiresAmplitudeControl(mAmplitude)) {
- areFeaturesSupported &= vibrator.hasAmplitudeControl();
+ areFeaturesSupported &= vibratorInfo.hasAmplitudeControl();
}
return areFeaturesSupported;
}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 3b286a7..17ac36f 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -21,7 +21,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
/**
* Representation of a single segment of a {@link VibrationEffect}.
@@ -65,7 +65,7 @@
*
* @hide
*/
- public abstract boolean areVibrationFeaturesSupported(@NonNull Vibrator vibrator);
+ public abstract boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo);
/**
* Returns true if this segment could be a haptic feedback effect candidate.
diff --git a/core/java/android/os/vibrator/VibratorInfoFactory.java b/core/java/android/os/vibrator/VibratorInfoFactory.java
new file mode 100644
index 0000000..d10d7ec
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorInfoFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.NonNull;
+import android.os.VibratorInfo;
+
+/**
+ * Factory for creating {@link VibratorInfo}s.
+ *
+ * @hide
+ */
+public final class VibratorInfoFactory {
+ /**
+ * Creates a single {@link VibratorInfo} that is an intersection of a given collection of
+ * {@link VibratorInfo}s. That is, the capabilities of the returned info will be an
+ * intersection of that of the provided infos.
+ *
+ * @param id the ID for the new {@link VibratorInfo}.
+ * @param vibratorInfos the {@link VibratorInfo}s from which to create a single
+ * {@link VibratorInfo}.
+ * @return a {@link VibratorInfo} that represents the intersection of {@code vibratorInfos}.
+ */
+ @NonNull
+ public static VibratorInfo create(int id, @NonNull VibratorInfo[] vibratorInfos) {
+ if (vibratorInfos.length == 0) {
+ return new VibratorInfo.Builder(id).build();
+ }
+ if (vibratorInfos.length == 1) {
+ // Create an equivalent info with the requested ID.
+ return new VibratorInfo(id, vibratorInfos[0]);
+ }
+ // Create a MultiVibratorInfo that intersects all the given infos and has the requested ID.
+ return new MultiVibratorInfo(id, vibratorInfos);
+ }
+
+ private VibratorInfoFactory() {}
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 50ebab7..abc8bf5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10657,6 +10657,14 @@
"assist_long_press_home_enabled";
/**
+ * Whether press and hold on nav handle can trigger search.
+ *
+ * @hide
+ */
+ public static final String SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED =
+ "search_press_hold_nav_handle_enabled";
+
+ /**
* Control whether Trust Agents are in active unlock or extend unlock mode.
* @hide
*/
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 75640bd..f3b4c6d 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -92,6 +92,7 @@
mapParcel.recycle();
if (buffer != null) {
mRankingMapFd.unmap(buffer);
+ mRankingMapFd.close();
}
}
} else {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 21f676e..94d8516 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -67,6 +67,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
+import com.android.internal.infra.AndroidFuture;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -1710,6 +1711,11 @@
Slog.i(TAG, "onProcessRestarted");
mHandler.sendEmptyMessage(MSG_PROCESS_RESTARTED);
}
+
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
}
void onDetectorRemoteException() {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index d9ee859..ccf8b67 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -227,6 +227,12 @@
public void stopDetection() {
HotwordDetectionService.this.onStopDetection();
}
+
+ @Override
+ public void registerRemoteStorageService(IDetectorSessionStorageService
+ detectorSessionStorageService) {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
};
@Override
diff --git a/core/java/android/service/voice/IDetectorSessionStorageService.aidl b/core/java/android/service/voice/IDetectorSessionStorageService.aidl
new file mode 100644
index 0000000..592373e
--- /dev/null
+++ b/core/java/android/service/voice/IDetectorSessionStorageService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * @hide
+ */
+oneway interface IDetectorSessionStorageService {
+ /**
+ * Called when a file open request is sent. Only files with the given names under the internal
+ * app storage, i.e., {@link Context#getFilesDir()} can be opened.
+ */
+ void openFile(in String filename, in AndroidFuture future);
+}
diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index 098536d..c76ac28 100644
--- a/core/java/android/service/voice/ISandboxedDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -24,6 +24,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
+import android.service.voice.IDetectorSessionStorageService;
import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
import android.service.voice.IDspHotwordDetectionCallback;
import android.view.contentcapture.IContentCaptureManager;
@@ -71,4 +72,10 @@
void ping(in IRemoteCallback callback);
void stopDetection();
+
+ /**
+ * Registers the interface stub to talk to the voice interaction service for initialization/
+ * detection unrelated functionalities.
+ */
+ void registerRemoteStorageService(in IDetectorSessionStorageService detectorSessionStorageService);
}
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 128bc0d..f1bc792 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -36,6 +36,7 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.infra.AndroidFuture;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -302,6 +303,11 @@
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> mCallback.onHotwordDetectionServiceRestarted()));
}
+
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
+ }
}
/** @hide */
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index cbe7666..d184b1e 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -40,7 +40,12 @@
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.IContentCaptureManager;
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.util.Objects;
+import java.util.concurrent.ExecutionException;
import java.util.function.IntConsumer;
/**
@@ -86,6 +91,8 @@
private ContentCaptureManager mContentCaptureManager;
@Nullable
private IRecognitionServiceManager mIRecognitionServiceManager;
+ @Nullable
+ private IDetectorSessionStorageService mDetectorSessionStorageService;
private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
@@ -154,6 +161,12 @@
public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
mIRecognitionServiceManager = manager;
}
+
+ @Override
+ public void registerRemoteStorageService(IDetectorSessionStorageService
+ detectorSessionStorageService) {
+ mDetectorSessionStorageService = detectorSessionStorageService;
+ }
};
@Override
@@ -323,4 +336,23 @@
}
}
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given file names under the
+ * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in
+ * {@link Context#getFilesDir()} can be opened.
+ */
+ @Override
+ public @Nullable FileInputStream openFileInput(@NonNull String filename) throws
+ FileNotFoundException {
+ try {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mDetectorSessionStorageService.openFile(filename, future);
+ ParcelFileDescriptor pfd = future.get();
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | InterruptedException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 9e0eb4b..b5448d4 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -25,6 +25,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.content.Context;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.os.Binder;
@@ -37,7 +38,10 @@
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.infra.AndroidFuture;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -58,18 +62,20 @@
private final Callback mCallback;
private final Executor mExecutor;
+ private final Context mContext;
private final IVoiceInteractionManagerService mManagerService;
private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
private final String mAttributionTag;
VisualQueryDetector(
IVoiceInteractionManagerService managerService,
- @NonNull @CallbackExecutor Executor executor,
- Callback callback, @Nullable String attributionTag) {
+ @NonNull @CallbackExecutor Executor executor, Callback callback, Context context,
+ @Nullable String attributionTag) {
mManagerService = managerService;
mCallback = callback;
mExecutor = executor;
mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
+ mContext = context;
mAttributionTag = attributionTag;
}
@@ -247,7 +253,7 @@
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
initAndVerifyDetector(options, sharedMemory,
- new InitializationStateListener(mExecutor, mCallback),
+ new InitializationStateListener(mExecutor, mCallback, mContext),
DETECTOR_TYPE_VISUAL_QUERY_DETECTOR, mAttributionTag);
}
@@ -332,9 +338,12 @@
private final Executor mExecutor;
private final Callback mCallback;
- InitializationStateListener(Executor executor, Callback callback) {
+ private final Context mContext;
+
+ InitializationStateListener(Executor executor, Callback callback, Context context) {
this.mExecutor = executor;
this.mCallback = callback;
+ this.mContext = context;
}
@Override
@@ -428,5 +437,22 @@
!TextUtils.isEmpty(errorMessage) ? errorMessage : "Error data is null");
}));
}
+ @Override
+ public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onOpenFile " + filename);
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
+ Slog.v(TAG, "onOpenFile: " + filename);
+ File f = new File(mContext.getFilesDir(), filename);
+ ParcelFileDescriptor pfd = null;
+ try {
+ Slog.d(TAG, "opened a file with ParcelFileDescriptor.");
+ pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
+ } finally {
+ future.complete(pfd);
+ }
+ }));
+ }
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 8cec17f..b48b7ec 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -965,7 +965,7 @@
}
VisualQueryDetector visualQueryDetector =
- new VisualQueryDetector(mSystemService, executor, callback,
+ new VisualQueryDetector(mSystemService, executor, callback, this,
getAttributionTag());
HotwordDetector visualQueryDetectorInitializationDelegate =
visualQueryDetector.getInitializationDelegate();
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 9656f36..7f313c1 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -38,6 +38,7 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
@@ -232,39 +233,68 @@
intent,
attributionSource,
new ModelDownloadListener() {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mIsTerminated = false;
+
@Override
public void onProgress(int completedPercent) {
- try {
- listener.onProgress(completedPercent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ try {
+ listener.onProgress(completedPercent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onSuccess() {
- try {
- listener.onSuccess();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onSuccess();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onScheduled() {
- try {
- listener.onScheduled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onScheduled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@Override
public void onError(int error) {
- try {
- listener.onError(error);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mLock) {
+ if (mIsTerminated) {
+ return;
+ }
+ mIsTerminated = true;
+ try {
+ listener.onError(error);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
});
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 751cd21..6e73a3c 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,7 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
@@ -78,11 +81,17 @@
private int mConnectionCount = 0;
private final InputMethodManager mImm;
+ private final RectF mTempRectF = new RectF();
+
+ private final Region mTempRegion = new Region();
+
+ private final Matrix mTempMatrix = new Matrix();
+
/**
* The handwrite-able View that is currently the target of a hovering stylus pointer. This is
* used to help determine whether the handwriting PointerIcon should be shown in
* {@link #onResolvePointerIcon(Context, MotionEvent)} so that we can reduce the number of calls
- * to {@link #findBestCandidateView(float, float)}.
+ * to {@link #findBestCandidateView(float, float, boolean)}.
*/
@Nullable
private WeakReference<View> mCachedHoverTarget = null;
@@ -184,8 +193,8 @@
final float y = motionEvent.getY(pointerIndex);
if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
mState.mExceedHandwritingSlop = true;
- View candidateView =
- findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
+ View candidateView = findBestCandidateView(mState.mStylusDownX,
+ mState.mStylusDownY, /* isHover */ false);
if (candidateView != null) {
if (candidateView == getConnectedView()) {
if (!candidateView.hasFocus()) {
@@ -393,13 +402,14 @@
final View cachedHoverTarget = getCachedHoverTarget();
if (cachedHoverTarget != null) {
final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
- if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget)
+ if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
+ /* isHover */ true)
&& shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
return cachedHoverTarget;
}
}
- final View candidateView = findBestCandidateView(hoverX, hoverY);
+ final View candidateView = findBestCandidateView(hoverX, hoverY, /* isHover */ true);
if (candidateView != null) {
mCachedHoverTarget = new WeakReference<>(candidateView);
@@ -429,14 +439,14 @@
* @param y the y coordinates of the stylus event, in the coordinates of the window.
*/
@Nullable
- private View findBestCandidateView(float x, float y) {
+ private View findBestCandidateView(float x, float y, boolean isHover) {
// If the connectedView is not null and do not set any handwriting area, it will check
// whether the connectedView's boundary contains the initial stylus position. If true,
// directly return the connectedView.
final View connectedView = getConnectedView();
if (connectedView != null) {
Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(handwritingArea, x, y, connectedView)
+ if (isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
&& shouldTriggerStylusHandwritingForView(connectedView)) {
return connectedView;
}
@@ -450,7 +460,7 @@
for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
final View view = viewInfo.getView();
final Rect handwritingArea = viewInfo.getHandwritingArea();
- if (!isInHandwritingArea(handwritingArea, x, y, view)
+ if (!isInHandwritingArea(handwritingArea, x, y, view, isHover)
|| !shouldTriggerStylusHandwritingForView(view)) {
continue;
}
@@ -546,15 +556,48 @@
* Return true if the (x, y) is inside by the given {@link Rect} with the View's
* handwriting bounds with offsets applied.
*/
- private static boolean isInHandwritingArea(@Nullable Rect handwritingArea,
- float x, float y, View view) {
+ private boolean isInHandwritingArea(@Nullable Rect handwritingArea,
+ float x, float y, View view, boolean isHover) {
if (handwritingArea == null) return false;
- return contains(handwritingArea, x, y,
+ if (!contains(handwritingArea, x, y,
view.getHandwritingBoundsOffsetLeft(),
view.getHandwritingBoundsOffsetTop(),
view.getHandwritingBoundsOffsetRight(),
- view.getHandwritingBoundsOffsetBottom());
+ view.getHandwritingBoundsOffsetBottom())) {
+ return false;
+ }
+
+ // The returned handwritingArea computed by ViewParent#getChildVisibleRect didn't consider
+ // the case where a view is stacking on top of the editor. (e.g. DrawerLayout, popup)
+ // We must check the hit region of the editor again, and avoid the case where another
+ // view on top of the editor is handling MotionEvents.
+ ViewParent parent = view.getParent();
+ if (parent == null) {
+ return true;
+ }
+
+ Region region = mTempRegion;
+ mTempRegion.set(0, 0, view.getWidth(), view.getHeight());
+ Matrix matrix = mTempMatrix;
+ matrix.reset();
+ if (!parent.getChildLocalHitRegion(view, region, matrix, isHover)) {
+ return false;
+ }
+
+ // It's not easy to extend the region by the given handwritingBoundsOffset. Instead, we
+ // create a rectangle surrounding the motion event location and check if this rectangle
+ // overlaps with the hit region of the editor.
+ float left = x - view.getHandwritingBoundsOffsetRight();
+ float top = y - view.getHandwritingBoundsOffsetBottom();
+ float right = Math.max(x + view.getHandwritingBoundsOffsetLeft(), left + 1);
+ float bottom = Math.max(y + view.getHandwritingBoundsOffsetTop(), top + 1);
+ RectF rectF = mTempRectF;
+ rectF.set(left, top, right, bottom);
+ matrix.mapRect(rectF);
+
+ return region.op(Math.round(rectF.left), Math.round(rectF.top),
+ Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
}
/**
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6c5f195..fabfed3 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -28,7 +28,6 @@
import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
-import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -285,15 +284,11 @@
return false;
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
- ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, outState,
- mPendingAlpha);
- updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, outState,
- mPendingAlpha);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, outState,
- mPendingAlpha);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, outState,
- mPendingAlpha);
+ final ArrayList<SurfaceParams> params = new ArrayList<>();
+ updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+ updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
+ updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+ updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
@@ -457,7 +452,7 @@
return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
}
- private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
+ private void updateLeashesForSide(@InternalInsetsSide int side, int offset,
ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha) {
final ArraySet<InsetsSourceControl> controls = mSideControlsMap.get(side);
if (controls == null) {
@@ -475,9 +470,9 @@
}
addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
- final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
- ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished)
- : inset != 0;
+ final boolean visible = mPendingFraction == 0 && source != null
+ ? source.isVisible()
+ : !mFinished || mShownOnFinish;
if (outState != null && source != null) {
outState.addSource(new InsetsSource(source)
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c6d8bd1..8ec7d67 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -666,9 +666,6 @@
/** Set of inset types for which an animation was started since last resetting this field */
private @InsetsType int mLastStartedAnimTypes;
- /** Set of inset types which cannot be controlled by the user animation */
- private @InsetsType int mDisabledUserAnimationInsetsTypes;
-
/** Set of inset types which are existing */
private @InsetsType int mExistingTypes = 0;
@@ -887,21 +884,11 @@
mState.set(newState, 0 /* types */);
@InsetsType int existingTypes = 0;
@InsetsType int visibleTypes = 0;
- @InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (int i = 0, size = newState.sourceSize(); i < size; i++) {
final InsetsSource source = newState.sourceAt(i);
@InsetsType int type = source.getType();
@AnimationType int animationType = getAnimationType(type);
- if (!source.isUserControllable()) {
- // The user animation is not allowed when visible frame is empty.
- disabledUserAnimationTypes |= type;
- if (animationType == ANIMATION_TYPE_USER) {
- // Existing user animation needs to be cancelled.
- animationType = ANIMATION_TYPE_NONE;
- cancelledUserAnimationTypes[0] |= type;
- }
- }
final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
if (consumer != null) {
consumer.updateSource(source, animationType);
@@ -931,28 +918,11 @@
}
InsetsState.traverse(mState, newState, mRemoveGoneSources);
- updateDisabledUserAnimationTypes(disabledUserAnimationTypes);
-
if (cancelledUserAnimationTypes[0] != 0) {
mHandler.post(() -> show(cancelledUserAnimationTypes[0]));
}
}
- private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) {
- @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes;
- if (diff != 0) {
- for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
- if (consumer.getControl() != null && (consumer.getType() & diff) != 0) {
- mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
- mHandler.post(mInvokeControllableInsetsChangedListeners);
- break;
- }
- }
- mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes;
- }
- }
-
private boolean captionInsetsUnchanged() {
if (CAPTION_ON_SHELL) {
return false;
@@ -1332,26 +1302,6 @@
+ " while an existing " + Type.toString(mTypesBeingCancelled)
+ " is being cancelled.");
}
- if (animationType == ANIMATION_TYPE_USER) {
- final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes;
- if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
- types &= ~mDisabledUserAnimationInsetsTypes;
-
- if ((disabledTypes & ime()) != 0) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
-
- if (fromIme
- && !mState.isSourceOrDefaultVisible(mImeSourceConsumer.getId(), ime())) {
- // We've requested IMM to show IME, but the IME is not controllable. We need to
- // cancel the request.
- setRequestedVisibleTypes(0 /* visibleTypes */, ime());
- if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
- notifyVisibilityChanged();
- }
- }
- }
- }
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
@@ -1954,7 +1904,7 @@
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
InsetsSource source = mState.peekSource(consumer.getId());
- if (consumer.getControl() != null && source != null && source.isUserControllable()) {
+ if (consumer.getControl() != null && source != null) {
result |= consumer.getType();
}
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index ff009ed..0d5704e 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -187,11 +187,6 @@
return (mFlags & flags) == flags;
}
- boolean isUserControllable() {
- // If mVisibleFrame is null, it will be the same area as mFrame.
- return mVisibleFrame == null || !mVisibleFrame.isEmpty();
- }
-
/**
* Calculates the insets this source will cause to a client window.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 92509c9..5e19c67 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9279,8 +9279,8 @@
}
while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
- ignoredParentLeft += parentGroup.mLeft;
- ignoredParentTop += parentGroup.mTop;
+ ignoredParentLeft += parentGroup.mLeft - parentGroup.mScrollX;
+ ignoredParentTop += parentGroup.mTop - parentGroup.mScrollY;
viewParent = parentGroup.getParent();
if (viewParent instanceof View) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1b1098d..7bdff8c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7361,6 +7361,90 @@
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (!child.hasIdentityMatrix()) {
+ matrix.preConcat(child.getInverseMatrix());
+ }
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+ matrix.preTranslate(-dx, -dy);
+
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ // Map the bounds of this view into the region's coordinates and clip the region.
+ final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
+ rect.set(0, 0, width, height);
+ matrix.mapRect(rect);
+
+ boolean notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.INTERSECT);
+
+ if (isHover) {
+ HoverTarget target = mFirstHoverTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final HoverTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit) {
+ target = mFirstHoverTarget;
+ while (notEmpty && target != null) {
+ final HoverTarget next = target.next;
+ final View hoveredView = target.child;
+
+ rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight,
+ hoveredView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
+ target = next;
+ }
+ }
+ } else {
+ TouchTarget target = mFirstTouchTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit) {
+ target = mFirstTouchTarget;
+ while (notEmpty && target != null) {
+ final TouchTarget next = target.next;
+ final View touchedView = target.child;
+
+ rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight,
+ touchedView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
+ target = next;
+ }
+ }
+ }
+
+ if (notEmpty && mParent != null) {
+ notEmpty = mParent.getChildLocalHitRegion(this, region, matrix, isHover);
+ }
+ return notEmpty;
+ }
+
+
private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
final int[] locationInWindow = new int[2];
view.getLocationInWindow(locationInWindow);
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 1020d2e..54bc348 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -686,6 +687,36 @@
}
/**
+ * Compute the region where the child can receive the {@link MotionEvent}s from the root view.
+ *
+ * <p> Given region where the child will accept {@link MotionEvent}s.
+ * Modify the region to the unblocked region where the child can receive the
+ * {@link MotionEvent}s from the view root.
+ * </p>
+ *
+ * <p> The given region is always clipped by the bounds of the parent views. When there are
+ * on-going {@link MotionEvent}s, this method also makes use of the event dispatching results to
+ * determine whether a sibling view will also block the child's hit region.
+ * </p>
+ *
+ * @param child a child View, whose hit region we want to compute.
+ * @param region the initial hit region where the child view will handle {@link MotionEvent}s,
+ * defined in the child coordinates. Will be overwritten to the result hit region.
+ * @param matrix the matrix that maps the given child view's coordinates to the region
+ * coordinates. It will be modified to a matrix that maps window coordinates to
+ * the result region's coordinates.
+ * @param isHover if true it will return the hover events' hit region, otherwise it will
+ * return the touch events' hit region.
+ * @return true if the returned region is not empty.
+ * @hide
+ */
+ default boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ region.setEmpty();
+ return false;
+ }
+
+ /**
* Unbuffered dispatch has been requested by a child of this view parent.
* This method is called by the View hierarchy to signal ancestors that a View needs to
* request unbuffered dispatch.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3aa610a..ddd3269 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -128,6 +128,7 @@
import android.graphics.PorterDuff;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
@@ -321,13 +322,6 @@
SystemProperties.getBoolean("persist.wm.debug.client_immersive_confirmation", false);
/**
- * Whether the client should compute the window frame on its own.
- * @hide
- */
- public static final boolean LOCAL_LAYOUT =
- SystemProperties.getBoolean("persist.debug.local_layout", true);
-
- /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
@@ -1911,8 +1905,8 @@
final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
- final boolean attachedFrameChanged = LOCAL_LAYOUT
- && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
+ final boolean attachedFrameChanged =
+ !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
@@ -2397,6 +2391,22 @@
}
@Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (child != mView) {
+ throw new IllegalArgumentException("child " + child + " is not the root view "
+ + mView + " managed by this ViewRootImpl");
+ }
+
+ RectF rectF = new RectF(0, 0, mWidth, mHeight);
+ matrix.mapRect(rectF);
+ // Note: don't apply scroll offset, because we want to know its
+ // visibility in the virtual canvas being given to the view hierarchy.
+ return region.op(Math.round(rectF.left), Math.round(rectF.top),
+ Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
+ }
+
+ @Override
public void bringChildToFront(View child) {
}
@@ -8292,8 +8302,7 @@
final int measuredWidth = mMeasuredWidth;
final int measuredHeight = mMeasuredHeight;
final boolean relayoutAsync;
- if (LOCAL_LAYOUT
- && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
+ if ((mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
&& mWindowAttributes.type != TYPE_APPLICATION_STARTING
&& mSyncSeqId <= mLastSyncSeqId
&& winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 3801188..ba87caa 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -22,6 +22,7 @@
import android.service.voice.HotwordRejectedResult;
import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionServiceFailure;
+import com.android.internal.infra.AndroidFuture;
/**
* @hide
@@ -113,4 +114,9 @@
/** Called when the hotword detection process is restarted */
void onProcessRestarted();
+
+ /**
+ * Called when a file open request is sent.
+ */
+ void onOpenFile(in String filename, in AndroidFuture future);
}
diff --git a/core/java/com/android/internal/widget/BigPictureNotificationImageView.java b/core/java/com/android/internal/widget/BigPictureNotificationImageView.java
index 3a7cf74..f95f5db 100644
--- a/core/java/com/android/internal/widget/BigPictureNotificationImageView.java
+++ b/core/java/com/android/internal/widget/BigPictureNotificationImageView.java
@@ -37,13 +37,16 @@
* Icon.loadDrawable().
*/
@RemoteViews.RemoteView
-public class BigPictureNotificationImageView extends ImageView {
+public class BigPictureNotificationImageView extends ImageView implements
+ NotificationDrawableConsumer {
private static final String TAG = BigPictureNotificationImageView.class.getSimpleName();
private final int mMaximumDrawableWidth;
private final int mMaximumDrawableHeight;
+ private NotificationIconManager mIconManager;
+
public BigPictureNotificationImageView(@NonNull Context context) {
this(context, null, 0, 0);
}
@@ -69,6 +72,19 @@
: R.dimen.notification_big_picture_max_height);
}
+
+ /**
+ * Sets an {@link NotificationIconManager} on this ImageView, which handles the loading of
+ * icons, instead of using the {@link LocalImageResolver} directly.
+ * If set, it overrides the behaviour of {@link #setImageIconAsync} and {@link #setImageIcon},
+ * and it expects that the content of this imageView is only updated calling these two methods.
+ *
+ * @param iconManager to be called, when the icon is updated
+ */
+ public void setIconManager(NotificationIconManager iconManager) {
+ mIconManager = iconManager;
+ }
+
@Override
@android.view.RemotableViewMethod(asyncImpl = "setImageURIAsync")
public void setImageURI(@Nullable Uri uri) {
@@ -84,11 +100,20 @@
@Override
@android.view.RemotableViewMethod(asyncImpl = "setImageIconAsync")
public void setImageIcon(@Nullable Icon icon) {
+ if (mIconManager != null) {
+ mIconManager.updateIcon(this, icon).run();
+ return;
+ }
+ // old code path
setImageDrawable(loadImage(icon));
}
/** @hide **/
public Runnable setImageIconAsync(@Nullable Icon icon) {
+ if (mIconManager != null) {
+ return mIconManager.updateIcon(this, icon);
+ }
+ // old code path
final Drawable drawable = loadImage(icon);
return () -> setImageDrawable(drawable);
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 5b6b360..42be784 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -149,6 +149,7 @@
private View mAppNameDivider;
private TouchDelegateComposite mTouchDelegate = new TouchDelegateComposite(this);
private ArrayList<MessagingLinearLayout.MessagingChild> mToRecycle = new ArrayList<>();
+ private boolean mPrecomputedTextEnabled = false;
public ConversationLayout(@NonNull Context context) {
super(context);
@@ -389,36 +390,37 @@
*/
@RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
+ bind(parseMessagingData(extras, /* usePrecomputedText= */ false));
+ }
+
+ @NonNull
+ private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
- List<Notification.MessagingStyle.Message> newMessages
- = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ List<Notification.MessagingStyle.Message> newMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
- List<Notification.MessagingStyle.Message> newHistoricMessages
- = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ List<Notification.MessagingStyle.Message> newHistoricMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
// mUser now set (would be nice to avoid the side effect but WHATEVER)
final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
- extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
+ extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem.class);
addRemoteInputHistoryToMessages(newMessages, history);
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
- // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
- // if they exist
final List<MessagingMessage> newMessagingMessages =
- createMessages(newMessages, /* isHistoric= */false,
- /* usePrecomputedText= */false);
+ createMessages(newMessages, /* isHistoric= */false, usePrecomputedText);
final List<MessagingMessage> newHistoricMessagingMessages =
- createMessages(newHistoricMessages, /* isHistoric= */true,
- /* usePrecomputedText= */false);
- // bind it, baby
- bindViews(user, showSpinner, unreadCount,
- newMessagingMessages,
- newHistoricMessagingMessages);
+ createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText);
+
+ return new MessagingData(user, showSpinner, unreadCount,
+ newHistoricMessagingMessages, newMessagingMessages);
}
/**
@@ -430,7 +432,33 @@
*/
@NonNull
public Runnable setDataAsync(Bundle extras) {
- return () -> setData(extras);
+ if (!mPrecomputedTextEnabled) {
+ return () -> setData(extras);
+ }
+
+ final MessagingData messagingData =
+ parseMessagingData(extras, /* usePrecomputedText= */ true);
+
+ return () -> {
+ finalizeInflate(messagingData.getHistoricMessagingMessages());
+ finalizeInflate(messagingData.getNewMessagingMessages());
+
+ bind(messagingData);
+ };
+ }
+
+ /**
+ * enable/disable precomputed text usage
+ * @hide
+ */
+ public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
+ mPrecomputedTextEnabled = precomputedTextEnabled;
+ }
+
+ private void finalizeInflate(List<MessagingMessage> historicMessagingMessages) {
+ for (MessagingMessage messagingMessage : historicMessagingMessages) {
+ messagingMessage.finalizeInflate();
+ }
}
@Override
@@ -460,17 +488,12 @@
}
}
+ private void bind(MessagingData messagingData) {
+ setUser(messagingData.getUser());
+ setUnreadCount(messagingData.getUnreadCount());
- private void bindViews(Person user,
- boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
- List<MessagingMessage> newHistoricMessagingMessages) {
- setUser(user);
- setUnreadCount(unreadCount);
- bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
- }
-
- private void bind(boolean showSpinner, List<MessagingMessage> messages,
- List<MessagingMessage> historicMessages) {
+ List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
+ List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
// Copy our groups, before they get clobbered
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
@@ -483,7 +506,7 @@
// Let's now create the views and reorder them accordingly
// side-effect: updates mGroups, mAddedGroups
- createGroupViews(groups, senders, showSpinner);
+ createGroupViews(groups, senders, messagingData.getShowSpinner());
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
@@ -585,7 +608,7 @@
// When collapsed, we're displaying the image message in a dedicated container
// on the right of the layout instead of inline. Let's add the isolated image there
- MessagingGroup messagingGroup = mGroups.get(mGroups.size() -1);
+ MessagingGroup messagingGroup = mGroups.get(mGroups.size() - 1);
MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
if (isolatedMessage != null) {
newMessage = isolatedMessage.getView();
@@ -1042,7 +1065,7 @@
}
if (visibleChildren > 0 && group.getVisibility() == GONE) {
group.setVisibility(VISIBLE);
- } else if (visibleChildren == 0 && group.getVisibility() != GONE) {
+ } else if (visibleChildren == 0 && group.getVisibility() != GONE) {
group.setVisibility(GONE);
}
}
@@ -1259,7 +1282,7 @@
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
- for (TouchDelegate delegate: mDelegates) {
+ for (TouchDelegate delegate : mDelegates) {
event.setLocation(x, y);
if (delegate.onTouchEvent(event)) {
return true;
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
new file mode 100644
index 0000000..85b0201
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.app.Person;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+final class MessagingData {
+ private final Person mUser;
+ private final boolean mShowSpinner;
+ private final List<MessagingMessage> mHistoricMessagingMessages;
+ private final List<MessagingMessage> mNewMessagingMessages;
+ private final int mUnreadCount;
+
+ MessagingData(Person user, boolean showSpinner,
+ List<MessagingMessage> historicMessagingMessages,
+ List<MessagingMessage> newMessagingMessages) {
+ this(user, showSpinner, /* unreadCount= */0,
+ historicMessagingMessages, newMessagingMessages);
+ }
+
+ MessagingData(Person user, boolean showSpinner,
+ int unreadCount,
+ List<MessagingMessage> historicMessagingMessages,
+ List<MessagingMessage> newMessagingMessages) {
+ mUser = user;
+ mShowSpinner = showSpinner;
+ mUnreadCount = unreadCount;
+ mHistoricMessagingMessages = historicMessagingMessages;
+ mNewMessagingMessages = newMessagingMessages;
+ }
+
+ public Person getUser() {
+ return mUser;
+ }
+
+ public boolean getShowSpinner() {
+ return mShowSpinner;
+ }
+
+ public List<MessagingMessage> getHistoricMessagingMessages() {
+ return mHistoricMessagingMessages;
+ }
+
+ public List<MessagingMessage> getNewMessagingMessages() {
+ return mNewMessagingMessages;
+ }
+
+ public int getUnreadCount() {
+ return mUnreadCount;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 83557cd..b6d7503 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -87,7 +87,7 @@
private ImageResolver mImageResolver;
private CharSequence mConversationTitle;
private ArrayList<MessagingLinearLayout.MessagingChild> mToRecycle = new ArrayList<>();
-
+ private boolean mPrecomputedTextEnabled = false;
public MessagingLayout(@NonNull Context context) {
super(context);
}
@@ -162,15 +162,23 @@
*/
@RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
+ bind(parseMessagingData(extras, /* usePrecomputedText= */false));
+ }
+
+ @NonNull
+ private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
- List<Notification.MessagingStyle.Message> newMessages
- = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ List<Notification.MessagingStyle.Message> newMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
- List<Notification.MessagingStyle.Message> newHistoricMessages
- = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
- setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
- RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
- extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
+ List<Notification.MessagingStyle.Message> newHistoricMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON,
+ Person.class));
+ RemoteInputHistoryItem[] history =
+ (RemoteInputHistoryItem[]) extras.getParcelableArray(
+ Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+ RemoteInputHistoryItem.class);
addRemoteInputHistoryToMessages(newMessages, history);
final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
@@ -178,10 +186,12 @@
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
- /* isHistoric= */true, /* usePrecomputedText= */ false);
+ /* isHistoric= */true, usePrecomputedText);
final List<MessagingMessage> newMessagingMessages =
- createMessages(newMessages, /* isHistoric= */false, /* usePrecomputedText= */false);
- bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+ createMessages(newMessages, /* isHistoric */false, usePrecomputedText);
+
+ return new MessagingData(user, showSpinner,
+ historicMessagingMessages, newMessagingMessages);
}
/**
@@ -193,7 +203,32 @@
*/
@NonNull
public Runnable setDataAsync(Bundle extras) {
- return () -> setData(extras);
+ if (!mPrecomputedTextEnabled) {
+ return () -> setData(extras);
+ }
+
+ final MessagingData messagingData =
+ parseMessagingData(extras, /* usePrecomputedText= */true);
+
+ return () -> {
+ finalizeInflate(messagingData.getHistoricMessagingMessages());
+ finalizeInflate(messagingData.getNewMessagingMessages());
+ bind(messagingData);
+ };
+ }
+
+ /**
+ * enable/disable precomputed text usage
+ * @hide
+ */
+ public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
+ mPrecomputedTextEnabled = precomputedTextEnabled;
+ }
+
+ private void finalizeInflate(List<MessagingMessage> historicMessagingMessages) {
+ for (MessagingMessage messagingMessage: historicMessagingMessages) {
+ messagingMessage.finalizeInflate();
+ }
}
@Override
@@ -218,17 +253,13 @@
}
}
- private void bindViews(Person user, boolean showSpinner,
- List<MessagingMessage> historicMessagingMessages,
- List<MessagingMessage> newMessagingMessages) {
- setUser(user);
- bind(showSpinner, historicMessagingMessages, newMessagingMessages);
- }
+ private void bind(MessagingData messagingData) {
+ setUser(messagingData.getUser());
- private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
- List<MessagingMessage> messages) {
+ List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
+ List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
- addMessagesToGroups(historicMessages, messages, showSpinner);
+ addMessagesToGroups(historicMessages, messages, messagingData.getShowSpinner());
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
diff --git a/core/java/com/android/internal/widget/NotificationDrawableConsumer.java b/core/java/com/android/internal/widget/NotificationDrawableConsumer.java
new file mode 100644
index 0000000..7c4d929
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationDrawableConsumer.java
@@ -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.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An interface for the class, who will use {@link NotificationIconManager} to load icons.
+ */
+public interface NotificationDrawableConsumer {
+
+ /**
+ * Sets a drawable as the content of this consumer.
+ *
+ * @param drawable the {@link Drawable} to set, or {@code null} to clear the content
+ */
+ void setImageDrawable(@Nullable Drawable drawable);
+}
diff --git a/core/java/com/android/internal/widget/NotificationIconManager.java b/core/java/com/android/internal/widget/NotificationIconManager.java
new file mode 100644
index 0000000..221845c
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationIconManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.drawable.Icon;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An interface used for Notification views to delegate handling the loading of icons.
+ */
+public interface NotificationIconManager {
+
+ /**
+ * Called when a new icon is provided to display.
+ *
+ * @param drawableConsumer a consumer, which can display the loaded drawable.
+ * @param icon the updated icon to be displayed.
+ *
+ * @return a {@link Runnable} that sets the drawable on the consumer
+ */
+ @NonNull
+ Runnable updateIcon(
+ @NonNull NotificationDrawableConsumer drawableConsumer,
+ @Nullable Icon icon
+ );
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9aa992b..b5d70d3 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -353,7 +353,7 @@
JNIEnv* env;
jmethodID methodId;
- ALOGD("Calling main entry %s", className.string());
+ ALOGD("Calling main entry %s", className.c_str());
env = getJNIEnv();
if (clazz == NULL || env == NULL) {
@@ -362,7 +362,7 @@
methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
if (methodId == NULL) {
- ALOGE("ERROR: could not find method %s.main(String[])\n", className.string());
+ ALOGE("ERROR: could not find method %s.main(String[])\n", className.c_str());
return UNKNOWN_ERROR;
}
@@ -378,7 +378,7 @@
strArray = env->NewObjectArray(numArgs, stringClass, NULL);
for (size_t i = 0; i < numArgs; i++) {
- jstring argStr = env->NewStringUTF(args[i].string());
+ jstring argStr = env->NewStringUTF(args[i].c_str());
env->SetObjectArrayElement(strArray, i, argStr);
}
@@ -1269,7 +1269,7 @@
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
- jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
+ jstring optionsStr = env->NewStringUTF(options.itemAt(i).c_str());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 624bd5f..69fc515 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -292,7 +292,7 @@
if (status) {
String8 message;
message.appendFormat("Failed to initialize display event receiver. status=%d", status);
- jniThrowRuntimeException(env, message.string());
+ jniThrowRuntimeException(env, message.c_str());
return 0;
}
@@ -316,7 +316,7 @@
if (status) {
String8 message;
message.appendFormat("Failed to schedule next vertical sync pulse. status=%d", status);
- jniThrowRuntimeException(env, message.string());
+ jniThrowRuntimeException(env, message.c_str());
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 061f669..833952d 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -340,7 +340,7 @@
if (status) {
String8 message;
message.appendFormat("Failed to initialize input event sender. status=%d", status);
- jniThrowRuntimeException(env, message.string());
+ jniThrowRuntimeException(env, message.c_str());
return 0;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index d7969cf..ed0081c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -137,6 +137,7 @@
optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Assist assist = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e129f7d..f55501a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7790,8 +7790,9 @@
android:process=":ui">
</activity>
<activity android:name="com.android.internal.app.PlatLogoActivity"
- android:theme="@style/Theme.Wallpaper.NoTitleBar.Fullscreen"
+ android:theme="@style/Theme.NoTitleBar.Fullscreen"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+ android:enableOnBackInvokedCallback="true"
android:icon="@drawable/platlogo"
android:process=":ui">
</activity>
diff --git a/core/res/res/drawable/focus_event_rotary_input_background.xml b/core/res/res/drawable/focus_event_rotary_input_background.xml
new file mode 100644
index 0000000..512cd68
--- /dev/null
+++ b/core/res/res/drawable/focus_event_rotary_input_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="focus_event_rotary_input_background"
+ android:shape="rectangle">
+
+ <!-- View background color -->
+ <solid android:color="#80741b47" />
+
+ <!-- View border color and width -->
+ <stroke android:width="1dp" android:color="#ffff00ff" />
+
+ <!-- The radius makes the corners rounded -->
+ <corners android:radius="4dp" />
+
+</shape>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a78b040..e0b6565 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -542,6 +542,9 @@
<bool name="config_goToSleepOnButtonPressTheaterMode">true</bool>
<!-- If this is true, long press on power button will be available from the non-interactive state -->
<bool name="config_supportLongPressPowerWhenNonInteractive">false</bool>
+ <!-- If this is true, short press on power button will be available whenever the default display
+ is on even if the device is non-interactive (dreaming). -->
+ <bool name="config_supportShortPressPowerWhenDefaultDisplayOn">false</bool>
<!-- If this is true, then keep dreaming when unplugging.
This config was formerly known as config_keepDreamingWhenUndocking.
@@ -6172,6 +6175,9 @@
<!-- Default value for Settings.ASSIST_TOUCH_GESTURE_ENABLED -->
<bool name="config_assistTouchGestureEnabledDefault">true</bool>
+ <!-- Default value for Settings.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED -->
+ <bool name="config_searchPressHoldNavHandleEnabledDefault">true</bool>
+
<!-- The maximum byte size of the information contained in the bundle of
HotwordDetectedResult. -->
<integer translatable="false" name="config_hotwordDetectedResultMaxBundleSize">0</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b8f93ca..4918bbe 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1968,6 +1968,7 @@
<java-symbol type="integer" name="config_keyguardDrawnTimeout" />
<java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
+ <java-symbol type="bool" name="config_supportShortPressPowerWhenDefaultDisplayOn" />
<java-symbol type="bool" name="config_wimaxEnabled" />
<java-symbol type="bool" name="show_ongoing_ime_switcher" />
<java-symbol type="color" name="config_defaultNotificationColor" />
@@ -4907,6 +4908,8 @@
<java-symbol type="bool" name="config_assistLongPressHomeEnabledDefault" />
<java-symbol type="bool" name="config_assistTouchGestureEnabledDefault" />
+ <java-symbol type="bool" name="config_searchPressHoldNavHandleEnabledDefault" />
+
<java-symbol type="integer" name="config_hotwordDetectedResultMaxBundleSize" />
<java-symbol type="dimen" name="config_wallpaperDimAmount" />
@@ -5197,6 +5200,7 @@
<java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
<java-symbol type="drawable" name="focus_event_pressed_key_background" />
+ <java-symbol type="drawable" name="focus_event_rotary_input_background" />
<java-symbol type="string" name="config_defaultShutdownVibrationFile" />
<java-symbol type="string" name="lockscreen_too_many_failed_attempts_countdown" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c14da29..7f56eb7 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -88,7 +88,7 @@
resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
- java_resources: [":ApkVerityTestCertDer"],
+ java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
data: [
":BinderDeathRecipientHelperApp1",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 31755ef..a358c4f 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1749,6 +1749,15 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+
+ <activity android:name="android.view.ViewGroupTestActivity"
+ android:label="ViewGroup Test"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/certs/Android.bp b/core/tests/coretests/certs/Android.bp
index 8d4ecf4..cefdc4d 100644
--- a/core/tests/coretests/certs/Android.bp
+++ b/core/tests/coretests/certs/Android.bp
@@ -13,3 +13,8 @@
name: "FrameworksCoreTests_unit_test_cert",
certificate: "unit_test",
}
+
+filegroup {
+ name: "FrameworksCoreTests_unit_test_cert_der",
+ srcs: ["unit_test.der"],
+}
diff --git a/core/tests/coretests/certs/README b/core/tests/coretests/certs/README
index 00917a1..b5c096e 100644
--- a/core/tests/coretests/certs/README
+++ b/core/tests/coretests/certs/README
@@ -2,3 +2,5 @@
development/tools/make_key unit_test '/CN=unit_test'
development/tools/make_key unit_test_diff '/CN=unit_test_diff'
+
+openssl x509 -in unit_test.x509.pem -out unit_test.der -outform der
diff --git a/core/tests/coretests/certs/unit_test.der b/core/tests/coretests/certs/unit_test.der
new file mode 100644
index 0000000..4dbbc49
--- /dev/null
+++ b/core/tests/coretests/certs/unit_test.der
Binary files differ
diff --git a/core/tests/coretests/res/layout/viewgroup_test.xml b/core/tests/coretests/res/layout/viewgroup_test.xml
new file mode 100644
index 0000000..04f4f52
--- /dev/null
+++ b/core/tests/coretests/res/layout/viewgroup_test.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Demonstrates adding/removing views from ViewGroup. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <EditText
+ android:id="@+id/view"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <EditText
+ android:id="@+id/view_scale"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:scaleX="0.5"
+ android:scaleY="2"
+ android:transformPivotX="0dp"
+ android:transformPivotY="0dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <EditText
+ android:id="@+id/view_translate"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:translationX="10dp"
+ android:translationY="20dp"
+ android:text="Hello World!"
+ android:background="#2F00FF00" />
+ <FrameLayout
+ android:layout_width="20dp"
+ android:layout_height="10dp">
+ <EditText
+ android:id="@+id/view_overlap_bottom"
+ android:layout_width="20dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"/>
+ <Button
+ android:id="@+id/view_overlap_top"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="20dp"
+ android:layout_height="10dp">
+ <EditText
+ android:id="@+id/view_cover_bottom"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:text="Hello World!"/>
+ <Button
+ android:id="@+id/view_cover_top"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/raw/fsverity_sig b/core/tests/coretests/res/raw/fsverity_sig
index b2f335d..2c28f0b 100644
--- a/core/tests/coretests/res/raw/fsverity_sig
+++ b/core/tests/coretests/res/raw/fsverity_sig
Binary files differ
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index a936cea..f9377fc 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -19,6 +19,8 @@
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT;
+import static android.app.Notification.DEFAULT_SOUND;
+import static android.app.Notification.DEFAULT_VIBRATE;
import static android.app.Notification.EXTRA_ANSWER_INTENT;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_CALL_PERSON;
@@ -35,6 +37,9 @@
import static android.app.Notification.EXTRA_PICTURE_ICON;
import static android.app.Notification.EXTRA_SUMMARY_TEXT;
import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.GROUP_ALERT_CHILDREN;
+import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.Notification.GROUP_KEY_SILENT;
import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON;
import static android.app.Notification.MessagingStyle.Message.KEY_TEXT;
@@ -511,6 +516,75 @@
}
@Test
+ public void testBuilder_setSilent_summaryBehavior_groupAlertChildren() {
+ Notification summaryNotif = new Notification.Builder(mContext, "channelId")
+ .setGroupSummary(true)
+ .setGroup("groupKey")
+ .setSilent(true)
+ .build();
+ assertEquals(GROUP_ALERT_CHILDREN, summaryNotif.getGroupAlertBehavior());
+ }
+
+ @Test
+ public void testBuilder_setSilent_childBehavior_groupAlertSummary() {
+ Notification childNotif = new Notification.Builder(mContext, "channelId")
+ .setGroupSummary(false)
+ .setGroup("groupKey")
+ .setSilent(true)
+ .build();
+ assertEquals(GROUP_ALERT_SUMMARY, childNotif.getGroupAlertBehavior());
+ }
+
+ @Test
+ public void testBuilder_setSilent_emptyGroupKey_groupKeySilent() {
+ Notification emptyGroupKeyNotif = new Notification.Builder(mContext, "channelId")
+ .setGroup("")
+ .setSilent(true)
+ .build();
+ assertEquals(GROUP_KEY_SILENT, emptyGroupKeyNotif.getGroup());
+ }
+
+ @Test
+ public void testBuilder_setSilent_vibrateNull() {
+ Notification silentNotif = new Notification.Builder(mContext, "channelId")
+ .setGroup("")
+ .setSilent(true)
+ .build();
+
+ assertNull(silentNotif.vibrate);
+ }
+
+ @Test
+ public void testBuilder_setSilent_soundNull() {
+ Notification silentNotif = new Notification.Builder(mContext, "channelId")
+ .setGroup("")
+ .setSilent(true)
+ .build();
+
+ assertNull(silentNotif.sound);
+ }
+
+ @Test
+ public void testBuilder_setSilent_noDefaultSound() {
+ Notification silentNotif = new Notification.Builder(mContext, "channelId")
+ .setGroup("")
+ .setSilent(true)
+ .build();
+
+ assertEquals(0, (silentNotif.defaults & DEFAULT_SOUND));
+ }
+
+ @Test
+ public void testBuilder_setSilent_noDefaultVibrate() {
+ Notification silentNotif = new Notification.Builder(mContext, "channelId")
+ .setGroup("")
+ .setSilent(true)
+ .build();
+
+ assertEquals(0, (silentNotif.defaults & DEFAULT_VIBRATE));
+ }
+
+ @Test
public void testCallStyle_getSystemActions_forIncomingCall() {
PendingIntent answerIntent = createPendingIntent("answer");
PendingIntent declineIntent = createPendingIntent("decline");
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index a84ac55..55ded9c 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -136,7 +136,11 @@
NotificationListenerService.RankingMap retrievedRankings =
retrievedRankingUpdate.getRankingMap();
assertNotNull(retrievedRankings);
- assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+ // The rankingUpdate file descriptor is only non-null in the new path.
+ if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+ }
NotificationListenerService.Ranking retrievedRanking =
new NotificationListenerService.Ranking();
assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
diff --git a/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java
new file mode 100644
index 0000000..60a0a2a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupGetChildLocalHitRegionTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import android.graphics.Matrix;
+import android.graphics.Region;
+import android.platform.test.annotations.Presubmit;
+import android.widget.LinearLayout;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.SmallTest;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test basic functions of ViewGroup.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ViewGroupTest
+ */
+@Presubmit
+@SmallTest
+public class ViewGroupGetChildLocalHitRegionTest {
+ @Rule
+ public ActivityScenarioRule<ViewGroupTestActivity> mScenarioRule =
+ new ActivityScenarioRule<>(ViewGroupTestActivity.class);
+
+ private LinearLayout mRoot;
+ private final int[] mRootLocation = new int[2];
+
+ @Before
+ public void setup() {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ mRoot = activity.findViewById(R.id.linear_layout);
+ mRoot.getLocationInWindow(mRootLocation);
+ });
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion() {
+ assertGetChildLocalHitRegion(R.id.view);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_withScale() {
+ assertGetChildLocalHitRegion(R.id.view_scale);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_withTranslate() {
+ assertGetChildLocalHitRegion(R.id.view_translate);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_overlap_noMotionEvent() {
+ assertGetChildLocalHitRegion(R.id.view_overlap_bottom);
+ }
+ @Test
+ public void testGetChildLocalHitRegion_overlap_withMotionEvent() {
+ // In this case, view_cover_bottom is partially covered by the view_cover_top.
+ // The returned region is the bounds of the bottom view subtract the bounds of the top view.
+ assertGetChildLocalHitRegion(R.id.view_overlap_top, R.id.view_overlap_bottom);
+ }
+
+ @Test
+ public void testGetChildLocalHitRegion_cover_withMotionEvent() {
+ // In this case, view_cover_bottom is completely covered by the view_cover_top.
+ // The returned region is expected to be empty.
+ assertGetChildLocalHitRegionEmpty(R.id.view_cover_top, R.id.view_cover_bottom);
+ }
+
+ private void injectMotionEvent(View view, boolean isHover) {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+
+ float x = location[0] + view.getWidth() / 2f;
+ float y = location[1] + view.getHeight() / 2f;
+
+ int action = isHover ? MotionEvent.ACTION_HOVER_ENTER : MotionEvent.ACTION_DOWN;
+ MotionEvent motionEvent = MotionEvent.obtain(/* downtime= */ 0, /* eventTime= */ 0, action,
+ x, y, /* pressure= */ 0, /* size= */ 0, /* metaState= */ 0,
+ /* xPrecision= */ 1, /* yPrecision= */ 1, /* deviceId= */0, /* edgeFlags= */0);
+
+ View rootView = view.getRootView();
+ rootView.dispatchPointerEvent(motionEvent);
+ }
+
+ private void assertGetChildLocalHitRegion(int viewId) {
+ assertGetChildLocalHitRegion(viewId, /* isHover= */ true);
+ assertGetChildLocalHitRegion(viewId, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion for a single view.
+ * @param viewId the viewId of the tested view.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegion(int viewId, boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View view = activity.findViewById(viewId);
+
+ Matrix actualMatrix = new Matrix();
+ Region actualRegion = new Region(0, 0, view.getWidth(), view.getHeight());
+ boolean actualNotEmpty = view.getParent()
+ .getChildLocalHitRegion(view, actualRegion, actualMatrix, isHover);
+
+ int[] windowLocation = new int[2];
+ view.getLocationInWindow(windowLocation);
+ Matrix expectMatrix = new Matrix();
+ expectMatrix.preScale(1 / view.getScaleX(), 1 / view.getScaleY());
+ expectMatrix.preTranslate(-windowLocation[0], -windowLocation[1]);
+
+ Region expectRegion = new Region(0, 0, view.getWidth(), view.getHeight());
+
+ assertThat(actualNotEmpty).isTrue();
+ assertThat(actualMatrix).isEqualTo(expectMatrix);
+ assertThat(actualRegion).isEqualTo(expectRegion);
+ });
+ }
+
+ private void assertGetChildLocalHitRegion(int viewIdTop, int viewIdBottom) {
+ assertGetChildLocalHitRegion(viewIdTop, viewIdBottom, /* isHover= */ true);
+ assertGetChildLocalHitRegion(viewIdTop, viewIdBottom, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion of a view that is covered by another view. It will
+ * inject {@link MotionEvent}s to the view on top first and then get the hit region of the
+ * bottom view.
+ *
+ * @param viewIdTop the view id of the test view on top.
+ * @param viewIdBottom the view id of the test view at the bottom.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegion(int viewIdTop, int viewIdBottom, boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View viewTop = activity.findViewById(viewIdTop);
+ View viewBottom = activity.findViewById(viewIdBottom);
+
+ injectMotionEvent(viewTop, isHover);
+
+ Matrix actualMatrix = new Matrix();
+ Region actualRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ boolean actualNotEmpty = viewBottom.getParent()
+ .getChildLocalHitRegion(viewBottom, actualRegion, actualMatrix, isHover);
+
+ int[] windowLocation = new int[2];
+ viewBottom.getLocationInWindow(windowLocation);
+ Matrix expectMatrix = new Matrix();
+ expectMatrix.preTranslate(-windowLocation[0], -windowLocation[1]);
+
+ Region expectRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ expectRegion.op(0, 0, viewTop.getWidth(), viewTop.getHeight(), Region.Op.DIFFERENCE);
+
+ assertThat(actualNotEmpty).isTrue();
+ assertThat(actualMatrix).isEqualTo(expectMatrix);
+ assertThat(actualRegion).isEqualTo(expectRegion);
+ });
+ }
+
+ private void assertGetChildLocalHitRegionEmpty(int viewIdTop, int viewIdBottom) {
+ assertGetChildLocalHitRegionEmpty(viewIdTop, viewIdBottom, /* isHover= */ true);
+ assertGetChildLocalHitRegionEmpty(viewIdTop, viewIdBottom, /* isHover= */ false);
+ }
+
+ /**
+ * Assert ViewParent#getChildLocalHitRegion returns an empty region for a view that is
+ * completely covered by another view. It will inject {@link MotionEvent}s to the view on top
+ * first and then get the hit region of the
+ * bottom view.
+ *
+ * @param viewIdTop the view id of the test view on top.
+ * @param viewIdBottom the view id of the test view at the bottom.
+ * @param isHover if true, check the hit region of the hover events. Otherwise, check the hit
+ * region of the touch events.
+ */
+ private void assertGetChildLocalHitRegionEmpty(int viewIdTop, int viewIdBottom,
+ boolean isHover) {
+ mScenarioRule.getScenario().onActivity(activity -> {
+ View viewTop = activity.findViewById(viewIdTop);
+ View viewBottom = activity.findViewById(viewIdBottom);
+
+ injectMotionEvent(viewTop, isHover);
+
+ Region actualRegion = new Region(0, 0, viewBottom.getWidth(), viewBottom.getHeight());
+ boolean actualNotEmpty = viewBottom.getParent()
+ .getChildLocalHitRegion(viewBottom, actualRegion, new Matrix(), isHover);
+
+ assertThat(actualNotEmpty).isFalse();
+ assertThat(actualRegion.isEmpty()).isTrue();
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupTestActivity.java b/core/tests/coretests/src/android/view/ViewGroupTestActivity.java
new file mode 100644
index 0000000..b94bda5
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class ViewGroupTestActivity extends Activity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.viewgroup_test);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
index 388a996..b4c72ca 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingTestUtil.java
@@ -21,7 +21,9 @@
import android.app.Instrumentation;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.Region;
import android.view.View;
import android.view.ViewGroup;
@@ -45,7 +47,7 @@
float handwritingBoundsOffsetRight, float handwritingBoundsOffsetBottom) {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final Context context = instrumentation.getTargetContext();
- // mock a parent so that HandwritingInitiator can get
+ // mock a parent so that HandwritingInitiator can get visible rect and hit region.
final ViewGroup parent = new ViewGroup(context) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
@@ -56,6 +58,14 @@
r.set(handwritingArea);
return true;
}
+
+ @Override
+ public boolean getChildLocalHitRegion(View child, Region region, Matrix matrix,
+ boolean isHover) {
+ matrix.reset();
+ region.set(handwritingArea);
+ return true;
+ }
};
View view = spy(new View(context));
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
index 1513654..a978e3b 100644
--- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -229,11 +229,12 @@
@Test
public void testSignatureGeneratedExternally() throws Exception {
var context = InstrumentationRegistry.getInstrumentation().getContext();
- byte[] cert = getClass().getClassLoader().getResourceAsStream("ApkVerityTestCert.der")
+ byte[] cert = getClass().getClassLoader().getResourceAsStream("unit_test.der")
.readAllBytes();
// The signature is generated by:
- // fsverity sign <(echo -n fs-verity) fsverity_sig --key=ApkVerityTestKey.pem \
- // --cert=ApkVerityTestCert.pem
+ // openssl pkcs8 -topk8 -nocrypt -in certs/unit_test.pk8 -out certs/unit_test.key.pem
+ // fsverity sign <(echo -n fs-verity) fsverity_sig --key=certs/unit_test.key.pem \
+ // --cert=certs/unit_test.x509.pem
byte[] sig = context.getResources().openRawResource(R.raw.fsverity_sig).readAllBytes();
// The fs-verity digest is generated by:
// fsverity digest --compact <(echo -n fs-verity)
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 8be489e..e875875 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -41,8 +41,6 @@
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
-import androidx.test.InstrumentationRegistry;
-
import com.android.internal.R;
import org.jetbrains.annotations.NotNull;
@@ -838,31 +836,29 @@
@Test
public void testAreVibrationFeaturesSupported_allSegmentsSupported() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
- .build());
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
assertTrue(VibrationEffect.createWaveform(
/* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(VibrationEffect.createWaveform(
/* timings= */ new long[] {1, 2, 3},
/* amplitudes= */ new int[] {10, 20, 40},
/* repeatIndex= */ 2)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(
VibrationEffect.startComposition()
.addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
.repeatEffectIndefinitely(TEST_ONE_SHOT)
.compose()
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testAreVibrationFeaturesSupported_withUnsupportedSegments() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1).build());
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
assertFalse(
VibrationEffect.startComposition()
@@ -872,7 +868,7 @@
/* amplitudes= */ new int[] {10, 20, 40},
/* repeatIndex= */ -1))
.compose()
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
@@ -996,13 +992,4 @@
return context;
}
-
- private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return info;
- }
- };
- }
}
diff --git a/core/tests/vibrator/src/android/os/VibratorInfoTest.java b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
index ff917aa..73cd464 100644
--- a/core/tests/vibrator/src/android/os/VibratorInfoTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorInfoTest.java
@@ -57,6 +57,17 @@
}
@Test
+ public void testHasFrequencyControl() {
+ VibratorInfo noCapabilities = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
+ assertFalse(noCapabilities.hasFrequencyControl());
+ VibratorInfo composeAndFrequencyControl = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
+ .setCapabilities(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+ .build();
+ assertTrue(composeAndFrequencyControl.hasFrequencyControl());
+ }
+
+ @Test
public void testHasCapabilities() {
VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
@@ -246,8 +257,13 @@
@Test
public void testEquals() {
- VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ VibratorInfo.Builder completeBuilder = new VibratorInfo.Builder(TEST_VIBRATOR_ID);
+ // Create a builder with a different ID, but same properties the same as the first one.
+ VibratorInfo.Builder completeBuilder2 = new VibratorInfo.Builder(TEST_VIBRATOR_ID + 2);
+
+ for (VibratorInfo.Builder builder :
+ new VibratorInfo.Builder[] {completeBuilder, completeBuilder2}) {
+ builder.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
.setSupportedEffects(VibrationEffect.EFFECT_CLICK)
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
.setPrimitiveDelayMax(100)
@@ -257,31 +273,43 @@
.setPwleSizeMax(20)
.setQFactor(2f)
.setFrequencyProfile(TEST_FREQUENCY_PROFILE);
+ }
VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
+ assertTrue(complete.equalContent(complete));
assertEquals(complete, completeBuilder.build());
+ assertTrue(complete.equalContent(completeBuilder.build()));
assertEquals(complete.hashCode(), completeBuilder.build().hashCode());
+ // The infos from the two builders should have equal content, but should not be equal due to
+ // their different IDs.
+ assertNotEquals(complete, completeBuilder2.build());
+ assertTrue(complete.equalContent(completeBuilder2.build()));
+
VibratorInfo completeWithComposeControl = completeBuilder
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
.build();
assertNotEquals(complete, completeWithComposeControl);
+ assertFalse(complete.equalContent(completeWithComposeControl));
VibratorInfo completeWithNoEffects = completeBuilder
.setSupportedEffects(new int[0])
.build();
assertNotEquals(complete, completeWithNoEffects);
+ assertFalse(complete.equalContent(completeWithNoEffects));
VibratorInfo completeWithUnknownEffects = completeBuilder
.setSupportedEffects(null)
.build();
assertNotEquals(complete, completeWithUnknownEffects);
+ assertFalse(complete.equalContent(completeWithUnknownEffects));
VibratorInfo completeWithDifferentPrimitiveDuration = completeBuilder
.setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
.build();
assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
+ assertFalse(complete.equalContent(completeWithDifferentPrimitiveDuration));
VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
@@ -291,31 +319,37 @@
TEST_AMPLITUDE_MAP))
.build();
assertNotEquals(complete, completeWithDifferentFrequencyProfile);
+ assertFalse(complete.equalContent(completeWithDifferentFrequencyProfile));
VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
.setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
.build();
assertNotEquals(complete, completeWithEmptyFrequencyProfile);
+ assertFalse(complete.equalContent(completeWithEmptyFrequencyProfile));
VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
assertNotEquals(complete, completeWithUnknownQFactor);
+ assertFalse(complete.equalContent(completeWithUnknownQFactor));
VibratorInfo completeWithDifferentQFactor = completeBuilder
.setQFactor(complete.getQFactor() + 3f)
.build();
assertNotEquals(complete, completeWithDifferentQFactor);
+ assertFalse(complete.equalContent(completeWithDifferentQFactor));
VibratorInfo unknownEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyEffectSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedEffects(new int[0])
.build();
assertNotEquals(unknownEffectSupport, knownEmptyEffectSupport);
+ assertFalse(unknownEffectSupport.equalContent(knownEmptyEffectSupport));
VibratorInfo unknownBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
VibratorInfo knownEmptyBrakingSupport = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
.setSupportedBraking(new int[0])
.build();
assertNotEquals(unknownBrakingSupport, knownEmptyBrakingSupport);
+ assertFalse(unknownBrakingSupport.equalContent(knownEmptyBrakingSupport));
}
@Test
@@ -335,4 +369,186 @@
assertEquals(original, restored);
assertEquals(original.hashCode(), restored.hashCode());
}
+
+ @Test
+ public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudeUnsupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+ // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
+ assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
+ assertFalse(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudeSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1).build();
+
+ // All amplitudes are min, max, or default. Requires no amplitude control.
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_withAmplitudeControl_allWaveformsSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
+
+ // All forms of amplitudes are valid when amplitude control is available.
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
+ assertTrue(info.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ waveformWithAmplitudes(7, 255, 0, 0, 60)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_compositionsWithSupportedPrimitivesSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose()));
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ /* scale= */ 0.2f,
+ /* delay= */ 200)
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_compositionsWithUnupportedPrimitivesUnsupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .compose()));
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .compose()));
+
+ info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
+ .build();
+
+ assertTrue(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addEffect(VibrationEffect.createWaveform(
+ // These timings are given either 0 or default amplitudes, which
+ // do not require vibrator's amplitude control.
+ /* timings= */ new long[] {1, 2, 3},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .compose()));
+ }
+
+ @Test
+ public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
+ .build();
+
+ // Not supported due to the TICK primitive, which the vibrator has no support for.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .compose()));
+ // Not supported due to the THUD effect, which the vibrator has no support for.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
+ .compose()));
+
+ info = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP)
+ .build();
+
+ // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
+ // DEFAULT), because the vibrator has no amplitude control.
+ assertFalse(info.areVibrationFeaturesSupported(
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
+ .addEffect(VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {10, 20, 255},
+ /* repeatIndex= */ -1))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
+ .compose()));
+ }
+
+ private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
+ long[] timings = new long[amplitudes.length];
+ for (int i = 0; i < timings.length; i++) {
+ timings[i] = i * 2; // Arbitrary timings.
+ }
+ return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
+ }
}
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index c559e34..cfa12bb 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -37,7 +37,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
-import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.test.TestLooper;
@@ -60,8 +59,6 @@
@Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
- private static final float TEST_TOLERANCE = 1e-5f;
-
private Context mContextSpy;
private Vibrator mVibratorSpy;
private TestLooper mTestLooper;
@@ -79,9 +76,6 @@
@Test
public void getId_returnsDefaultId() {
assertEquals(-1, mVibratorSpy.getId());
- assertEquals(-1, new SystemVibrator.NoVibratorInfo().getId());
- assertEquals(-1, new SystemVibrator.MultiVibratorInfo(new VibratorInfo[] {
- VibratorInfo.EMPTY_VIBRATOR_INFO, VibratorInfo.EMPTY_VIBRATOR_INFO }).getId());
}
@Test
@@ -95,53 +89,6 @@
}
@Test
- public void areEffectsSupported_noVibrator_returnsAlwaysNo() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void areEffectsSupported_unsupportedInOneVibrator_returnsNo() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setSupportedEffects(new int[0])
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void areEffectsSupported_unknownInOneVibrator_returnsUnknown() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_supportedInAllVibrators_returnsYes() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
- assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
- }
-
- @Test
public void arePrimitivesSupported_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.arePrimitivesSupported(new int[0]).length);
assertEquals(1, mVibratorSpy.arePrimitivesSupported(
@@ -152,39 +99,6 @@
}
@Test
- public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_unsupportedInOneVibrator_returnsFalse() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void arePrimitivesSupported_supportedInAllVibrators_returnsTrue() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
- assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
public void getPrimitivesDurations_returnsArrayOfSameSize() {
assertEquals(0, mVibratorSpy.getPrimitiveDurations(new int[0]).length);
assertEquals(1, mVibratorSpy.getPrimitiveDurations(
@@ -195,245 +109,6 @@
}
@Test
- public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void getPrimitivesDurations_unsupportedInOneVibrator_returnsZero() {
- VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
- assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void getPrimitivesDurations_supportedInAllVibrators_returnsMaxDuration() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
- assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
- }
-
- @Test
- public void getQFactorAndResonantFrequency_noVibrator_returnsNaN() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
-
- assertTrue(Float.isNaN(info.getQFactor()));
- assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
- }
-
- @Test
- public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(1f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(2f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
-
- assertTrue(Float.isNaN(info.getQFactor()));
- assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
- assertEmptyFrequencyProfileAndControl(info);
-
- // One vibrator with values undefined.
- VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, thirdVibrator});
-
- assertTrue(Float.isNaN(info.getQFactor()));
- assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(10f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
- /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setQFactor(10f)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
- /* resonantFrequencyHz= */ 11, 5, 1, null))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator});
-
- assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
- assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
-
- // No frequency range defined.
- assertTrue(info.getFrequencyProfile().isEmpty());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
-
- @Test
- public void getFrequencyProfile_noVibrator_returnsEmpty() {
- VibratorInfo info = new SystemVibrator.NoVibratorInfo();
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, differentResonantFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_missingValues_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
- new float[] { 0, 1 }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingResonantFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingMinFrequency});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
- new float[] { 0, 1 }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
-
- assertEmptyFrequencyProfileAndControl(info);
-
- VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
- new float[] { 0, 1, 1, 0 }))
- .build();
- VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
- new float[] { 0, 1, 1, 0 }))
- .build();
- VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
- new float[] { 0, 1, 1, 0 }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
-
- assertEmptyFrequencyProfileAndControl(info);
- }
-
- @Test
- public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
- VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
- new float[] { 0.5f, 1, 1, 0.5f }))
- .build();
- VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
- new float[] { 1, 1, 1 }))
- .build();
- VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
- .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
- new float[] { 0.8f, 1, 0.8f, 0.5f }))
- .build();
- VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
-
- assertEquals(
- new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
- info.getFrequencyProfile());
- assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
-
- // Third vibrator without frequency control capability.
- thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
- .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
- new float[] { 0.8f, 1, 0.8f, 0.5f }))
- .build();
- info = new SystemVibrator.MultiVibratorInfo(
- new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
-
- assertEquals(
- new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
- info.getFrequencyProfile());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
-
- @Test
public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
VibratorManager mockVibratorManager = mock(VibratorManager.class);
when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
@@ -577,212 +252,4 @@
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
-
- @Test
- public void areVibrationFeaturesSupported_noAmplitudeControl_fractionalAmplitudes() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedEffects(VibrationEffect.EFFECT_THUD)
- .build());
-
- // Have at least one fractional amplitude (amplitude not min (0) or max (255) or DEFAULT).
- assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30)));
- assertFalse(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 255)));
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_noAmplitudeControl_nonFractionalAmplitudes() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedEffects(VibrationEffect.EFFECT_THUD)
- .build());
-
- // All amplitudes are min, max, or default. Requires no amplitude control.
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_withAmplitudeControl() {
- Vibrator vibrator =
- createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
- .build());
-
- // All forms of amplitudes are valid when amplitude control is available.
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(255, 0, VibrationEffect.DEFAULT_AMPLITUDE, 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3}, /* repeatIndex= */ -1)));
- assertTrue(vibrator.areVibrationFeaturesSupported(waveformWithAmplitudes(10, 30, 50)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- waveformWithAmplitudes(7, 255, 0, 0, 60)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, VibrationEffect.DEFAULT_AMPLITUDE)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 255)));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.createOneShot(20, /* amplitude= */ 40)));
- }
-
- @Test
- public void areVibrationFeaturesSupported_primitiveCompositionsWithSupportedPrimitives() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .compose()));
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- /* scale= */ 0.2f,
- /* delay= */ 200)
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_primitiveCompositionsWithUnupportedPrimitives() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .build());
-
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .compose()));
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_composedEffects_allComponentsSupported() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .compose()));
-
- vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_CLICK)
- .build());
-
- assertTrue(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .addEffect(VibrationEffect.createWaveform(
- // These timings are given either 0 or default amplitudes, which
- // do not require vibrator's amplitude control.
- /* timings= */ new long[] {1, 2, 3},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
- .compose()));
- }
-
- @Test
- public void areVibrationFeaturesSupported_composedEffects_someComponentsUnupported() {
- Vibrator vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_POP)
- .build());
-
- // Not supported due to the TICK primitive, which the vibrator has no support for.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .compose()));
- // Not supported due to the THUD effect, which the vibrator has no support for.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD))
- .compose()));
-
- vibrator = createVibratorWithCustomInfo(new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 10)
- .setSupportedEffects(VibrationEffect.EFFECT_POP)
- .build());
-
- // Not supported due to fractional amplitudes (amplitudes not min (0) or max (255) or
- // DEFAULT), because the vibrator has no amplitude control.
- assertFalse(vibrator.areVibrationFeaturesSupported(
- VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD)
- .addEffect(VibrationEffect.createWaveform(
- /* timings= */ new long[] {1, 2, 3},
- /* amplitudes= */ new int[] {10, 20, 255},
- /* repeatIndex= */ -1))
- .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_POP))
- .compose()));
- }
-
- /**
- * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
- */
- void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
- assertTrue(info.getFrequencyProfile().isEmpty());
- assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
- }
-
- private Vibrator createVibratorWithCustomInfo(VibratorInfo info) {
- return new SystemVibrator(mContextSpy) {
- @Override
- public VibratorInfo getInfo() {
- return info;
- }
- };
- }
-
- private static VibrationEffect waveformWithAmplitudes(int...amplitudes) {
- long[] timings = new long[amplitudes.length];
- for (int i = 0; i < timings.length; i++) {
- timings[i] = i * 2; // Arbitrary timings.
- }
- return VibrationEffect.createWaveform(timings, amplitudes, /* repeatIndex= */ -1);
- }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
new file mode 100644
index 0000000..fc31ac4
--- /dev/null
+++ b/core/tests/vibrator/src/android/os/vibrator/MultiVibratorInfoTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiVibratorInfoTest {
+ private static final float TEST_TOLERANCE = 1e-5f;
+
+ @Test
+ public void testGetId() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(new int[0])
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 3,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(3, info.getId());
+ }
+
+ @Test
+ public void testIsEffectSupported_supportedInAllVibrators_returnsYes() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsEffectSupported_unsupportedInOneVibrator_returnsNo() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setSupportedEffects(new int[0])
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsEffectSupported_unknownInOneVibrator_returnsUnknown() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
+ assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
+ info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ }
+
+ @Test
+ public void testIsPrimitiveSupported_unsupportedInOneVibrator_returnsFalse() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testIsPrimitiveSupported_supportedInAllVibrators_returnsTrue() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetPrimitiveDuration_unsupportedInOneVibrator_returnsZero() {
+ VibratorInfo supportedVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
+
+ assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetPrimitiveDuration_supportedInAllVibrators_returnsMaxDuration() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
+ }
+
+ @Test
+ public void testGetQFactorAndResonantFrequency_differentValues_returnsNaN() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(1f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(2f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
+
+ // One vibrator with values undefined.
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, thirdVibrator});
+
+ assertTrue(Float.isNaN(info.getQFactor()));
+ assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetQFactorAndResonantFrequency_sameValues_returnsValue() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setQFactor(10f)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 11, 5, 1, null))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo});
+
+ assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
+ assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+ // No frequency range defined.
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+
+ @Test
+ public void testGetFrequencyProfile_differentResonantFrequencyOrResolutions_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, differentResonantFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, differentFrequencyResolution});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_missingValues_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+ VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
+ new float[] { 0, 1 }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingResonantFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingMinFrequency});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
+ new float[] { 0, 1 }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingFrequencyResolution});
+
+ assertEmptyFrequencyProfileAndControl(info);
+
+ VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, missingMaxAmplitudes});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0, 1, 1, 0 }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, unalignedMinFrequency, thirdVibrator});
+
+ assertEmptyFrequencyProfileAndControl(info);
+ }
+
+ @Test
+ public void testGetFrequencyProfile_alignedProfiles_returnsIntersection() {
+ VibratorInfo firstInfo = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+ new float[] { 0.5f, 1, 1, 0.5f }))
+ .build();
+ VibratorInfo secondInfo = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 1, 1, 1 }))
+ .build();
+ VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+
+ VibratorInfo info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+ // Third vibrator without frequency control capability.
+ thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ info = new MultiVibratorInfo(/* id= */ 1,
+ new VibratorInfo[]{firstInfo, secondInfo, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+
+ /**
+ * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+ */
+ private void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
+}
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 8268077..4f5f3c0 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -25,18 +25,14 @@
import static org.testng.Assert.assertThrows;
import android.os.Parcel;
-import android.os.SystemVibrator;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class PrebakedSegmentTest {
@Test
@@ -149,121 +145,121 @@
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_TICK,
VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_DOUBLE_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackEnabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_TICK,
VibrationEffect.EFFECT_CLICK,
VibrationEffect.EFFECT_DOUBLE_CLICK,
VibrationEffect.EFFECT_HEAVY_CLICK);
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithFallback_fallbackDisabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_DOUBLE_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_HEAVY_CLICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_THUD,
VibrationEffect.EFFECT_POP,
VibrationEffect.EFFECT_TEXTURE_TICK);
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackEnabled_noVibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_vibratorSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(
VibrationEffect.EFFECT_THUD,
VibrationEffect.EFFECT_POP,
VibrationEffect.EFFECT_TEXTURE_TICK);
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertTrue(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_idsWithNoFallback_fallbackDisabled_noVibSupport() {
- Vibrator vibrator = createVibratorWithSupportedEffects(new int[0]);
+ VibratorInfo info = createVibratorInfoWithSupportedEffects(new int[0]);
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_THUD)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_POP)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
assertFalse(createSegmentWithoutFallback(VibrationEffect.EFFECT_TEXTURE_TICK)
- .areVibrationFeaturesSupported(vibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
@@ -283,14 +279,9 @@
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
- private static Vibrator createVibratorWithSupportedEffects(int... supportedEffects) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return new VibratorInfo.Builder(/* id= */ 1)
- .setSupportedEffects(supportedEffects)
- .build();
- }
- };
+ private static VibratorInfo createVibratorInfoWithSupportedEffects(int... supportedEffects) {
+ return new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(supportedEffects)
+ .build();
}
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 6f5adcd..ec5a084 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -25,18 +25,14 @@
import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
-import android.os.SystemVibrator;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.VibratorInfo;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class PrimitiveSegmentTest {
private static final float TOLERANCE = 1e-2f;
@@ -146,15 +142,15 @@
public void testVibrationFeaturesSupport_primitiveSupportedByVibrator() {
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK)));
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD)));
assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
}
@@ -162,15 +158,15 @@
public void testVibrationFeaturesSupport_primitiveNotSupportedByVibrator() {
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD)));
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK)));
assertFalse(createSegment(VibrationEffect.Composition.PRIMITIVE_THUD)
.areVibrationFeaturesSupported(
- createVibratorWithSupportedPrimitive(
+ createVibratorInfoWithSupportedPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)));
}
@@ -193,15 +189,10 @@
return new PrimitiveSegment(primitiveId, 0.2f, 10);
}
- private static Vibrator createVibratorWithSupportedPrimitive(int primitiveId) {
- return new SystemVibrator(InstrumentationRegistry.getContext()) {
- @Override
- public VibratorInfo getInfo() {
- return new VibratorInfo.Builder(/* id= */ 1)
- .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
- .setSupportedPrimitive(primitiveId, 10)
- .build();
- }
- };
+ private static VibratorInfo createVibratorInfoWithSupportedPrimitive(int primitiveId) {
+ return new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(primitiveId, 10)
+ .build();
}
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 68870e5..5caa86b 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -23,31 +23,21 @@
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class RampSegmentTest {
private static final float TOLERANCE = 1e-2f;
- @Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock
- private Vibrator mVibrator;
-
@Test
public void testCreation() {
RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -147,71 +137,71 @@
@Test
public void testVibrationFeaturesSupport_amplitudeAndFrequencyControls_supported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
// Increasing amplitude
- assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
// Increasing frequency
- assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 0, 1, 10).areVibrationFeaturesSupported(info));
// Decreasing amplitude
- assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
// Decreasing frequency
- assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 10).areVibrationFeaturesSupported(info));
// Zero duration
- assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(mVibrator));
+ assertTrue(new RampSegment(0.5f, 0.5f, 1, 0, 0).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noAmplitudeControl_unsupportedForChangingAmplitude() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
// Test with increasing/decreasing amplitudes.
- assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0.5f, 1, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(1, 0.5f, 0, 0, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noAmplitudeControl_fractionalAmplitudeUnsupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0.2f, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0.2f, 0, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0.2f, 0, 0, 0, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingZeroAmplitude_supported() {
RampSegment amplitudeZeroWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
RampSegment amplitudeZeroWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeZeroWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeZeroWithDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingOneAmplitude_supported() {
RampSegment amplitudeOneWithIncreasingFrequency = new RampSegment(1, 1, 0.5f, 0.8f, 10);
RampSegment amplitudeOneWithDecreasingFrequency = new RampSegment(1, 1, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(amplitudeOneWithIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(amplitudeOneWithDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
@@ -220,52 +210,52 @@
new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.5f, 0.8f, 10);
RampSegment defaultAmplitudeDecreasingFrequency =
new RampSegment(DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0.8f, 0.5f, 10);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ true);
- assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(mVibrator));
- assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(defaultAmplitudeIncreasingFrequency.areVibrationFeaturesSupported(info));
+ assertTrue(defaultAmplitudeDecreasingFrequency.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noFrequencyControl_unsupportedForChangingFrequency() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
// Test with increasing/decreasing frequencies.
- assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0.4f, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0.4f, 0.2f, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_noFrequencyControl_fractionalFrequencyUnsupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
- assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(mVibrator));
- assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(mVibrator));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0.2f, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0.2f, 0, 10).areVibrationFeaturesSupported(info));
+ assertFalse(new RampSegment(0, 0, 0, 0.2f, 10).areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_unchangingZeroFrequency_supported() {
RampSegment frequencyZeroWithIncreasingAmplitude = new RampSegment(0.1f, 1, 0, 0, 10);
RampSegment frequencyZeroWithDecreasingAmplitude = new RampSegment(1, 0.1f, 0, 0, 10);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info =
+ createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ false);
- assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
- assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+ assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ info = createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true);
- assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
- assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(frequencyZeroWithIncreasingAmplitude.areVibrationFeaturesSupported(info));
+ assertTrue(frequencyZeroWithDecreasingAmplitude.areVibrationFeaturesSupported(info));
}
@Test
@@ -274,4 +264,17 @@
// duration checked in VibrationEffect implementations.
assertTrue(new RampSegment(0.5f, 1, 0, 0, 5_000).isHapticFeedbackCandidate());
}
+
+ private static VibratorInfo createVibInfo(
+ boolean hasAmplitudeControl, boolean hasFrequencyControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ long capabilities = 0;
+ if (hasAmplitudeControl) {
+ capabilities |= IVibrator.CAP_AMPLITUDE_CONTROL;
+ }
+ if (hasFrequencyControl) {
+ capabilities |= (IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+ return builder.setCapabilities(capabilities).build();
+ }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 34bb892..44db306 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -21,31 +21,20 @@
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.vibrator.IVibrator;
import android.os.Parcel;
import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
+import org.junit.runners.JUnit4;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public class StepSegmentTest {
private static final float TOLERANCE = 1e-2f;
-
- @Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock
- private Vibrator mVibrator;
-
@Test
public void testCreation() {
StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequencyHz= */ 1f,
@@ -160,26 +149,26 @@
public void testVibrationFeaturesSupport_zeroAmplitude_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_maxAmplitude_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
@@ -189,60 +178,60 @@
/* amplitude= */ VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0,
/* duration= */ 0);
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_fractionalAmplitude_hasAmplitudeCtrl_supported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true);
assertTrue(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
- .areVibrationFeaturesSupported(mVibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_fractionalAmplitude_hasNoAmplitudeCtrl_notSupported() {
- when(mVibrator.hasAmplitudeControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForAmplitude(/* hasAmplitudeControl= */ false);
assertFalse(new StepSegment(/* amplitude= */ 0.2f, /* frequencyHz= */ 0, /* duration= */ 0)
- .areVibrationFeaturesSupported(mVibrator));
+ .areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_zeroFrequency_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_nonZeroFrequency_hasFrequencyCtrl_supported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(true);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ true);
- assertTrue(segment.areVibrationFeaturesSupported(mVibrator));
+ assertTrue(segment.areVibrationFeaturesSupported(info));
}
@Test
public void testVibrationFeaturesSupport_nonZeroFrequency_hasNoFrequencyCtrl_notSupported() {
StepSegment segment =
new StepSegment(/* amplitude= */ 0f, /* frequencyHz= */ 0.2f, /* duration= */ 0);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
+ VibratorInfo info = createVibInfoForFrequency(/* hasFrequencyControl= */ false);
- assertFalse(segment.areVibrationFeaturesSupported(mVibrator));
+ assertFalse(segment.areVibrationFeaturesSupported(info));
}
@Test
@@ -251,4 +240,21 @@
// duration checked in VibrationEffect implementations.
assertTrue(new StepSegment(0, 0, 5_000).isHapticFeedbackCandidate());
}
+
+ private static VibratorInfo createVibInfoForAmplitude(boolean hasAmplitudeControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ if (hasAmplitudeControl) {
+ builder.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ }
+ return builder.build();
+ }
+
+ private static VibratorInfo createVibInfoForFrequency(boolean hasFrequencyControl) {
+ VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1);
+ if (hasFrequencyControl) {
+ builder.setCapabilities(
+ IVibrator.CAP_FREQUENCY_CONTROL | IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+ }
+ return builder.build();
+ }
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java b/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java
new file mode 100644
index 0000000..df4822f
--- /dev/null
+++ b/core/tests/vibrator/src/android/os/vibrator/VibratorInfoFactoryTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VibratorInfoFactoryTest {
+
+ @Test
+ public void testCreatedInfo_hasTheRequestedId() {
+ // Empty info list.
+ VibratorInfo infoFromEmptyInfos =
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {});
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ 1)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+ VibratorInfo infoFromOneInfo =
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info1});
+ VibratorInfo infoFromTwoInfos =
+ VibratorInfoFactory.create(/* id= */ -3, new VibratorInfo[] {info1, info2});
+
+ assertEquals(3, infoFromEmptyInfos.getId());
+ assertEquals(-1, infoFromOneInfo.getId());
+ assertEquals(-3, infoFromTwoInfos.getId());
+ }
+
+ @Test
+ public void testCreatedInfo_fromEmptyVibratorInfos_returnsEmptyVibratorInfo() {
+ VibratorInfo info = VibratorInfoFactory.create(/* id= */ 2, new VibratorInfo[] {});
+
+ assertEqualContent(VibratorInfo.EMPTY_VIBRATOR_INFO, info);
+ }
+
+ @Test
+ public void testCreatedInfo_fromSingleVibratorInfo_hasEqualContent() {
+ VibratorInfo info = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_THUD)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 30)
+ .build();
+
+ VibratorInfo createdInfo =
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info});
+
+ assertEqualContent(info, createdInfo);
+ }
+
+ @Test
+ public void testCreatedInfo_hasEqualContentRegardlessOfSourceInfoOrder() {
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .build();
+
+ assertEqualContent(
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info1, info2}),
+ VibratorInfoFactory.create(/* id= */ -1, new VibratorInfo[] {info2, info1}));
+ }
+
+ @Test
+ public void testCreatedInfoContents() {
+ VibratorInfo info1 = new VibratorInfo.Builder(/* id= */ -1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_FREQUENCY_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_POP)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 5)
+ .build();
+ VibratorInfo info2 = new VibratorInfo.Builder(/* id= */ -2)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP, VibrationEffect.EFFECT_THUD)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 20)
+ .build();
+ VibratorInfo info3 = new VibratorInfo.Builder(/* id= */ -3)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
+
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedEffects(VibrationEffect.EFFECT_POP)
+ .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 20)
+ .build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info1, info2}));
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info2, info3}));
+ assertEquals(
+ new VibratorInfo.Builder(/* id= */ 3).build(),
+ VibratorInfoFactory.create(/* id= */ 3, new VibratorInfo[] {info1, info3}));
+ }
+
+ private static void assertEqualContent(VibratorInfo info1, VibratorInfo info2) {
+ assertTrue(info1.equalContent(info2));
+ }
+}
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index f639521..b5fb13d 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -124,8 +124,6 @@
/**
* Creates a new gainmap using the provided gainmap as the metadata source and the provided
* bitmap as the replacement for the gainmapContents
- * TODO: Make public, it's useful
- * @hide
*/
public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index ef93a33..be1b9b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common.split;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -60,7 +61,8 @@
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW,
+ WINDOWING_MODE_FREEFORM};
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
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 1d46e75..633f627 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
@@ -18,6 +18,7 @@
import android.R
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -301,6 +302,24 @@
}
}
+ /** Move a desktop app to split screen. */
+ fun moveToSplit(task: RunningTaskInfo) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: moveToSplit taskId=%d",
+ task.taskId
+ )
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ wct.setBounds(task.token, null)
+ wct.setDensityDpi(task.token, getDefaultDensityDpi())
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/**
* The second part of the animated move to desktop transition, called after
* {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index b872267..f25110a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -123,14 +123,6 @@
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -166,10 +158,6 @@
// early bail out if the change was caused by keyguard showing up
return;
}
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mPipBoundsState.isStashed()) {
// don't move when stashed
return;
@@ -187,10 +175,6 @@
}
private void updatePipPositionForKeepClearAreas() {
- if (!mEnablePipKeepClearAlgorithm) {
- // early bail out if the keep clear areas feature is disabled
- return;
- }
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
return;
@@ -343,19 +327,17 @@
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
if (mPipDisplayLayoutState.getDisplayId() == displayId) {
- if (mEnablePipKeepClearAlgorithm) {
- mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- mMainExecutor.removeCallbacks(
- mMovePipInResponseToKeepClearAreasChangeCallback);
- mMainExecutor.executeDelayed(
- mMovePipInResponseToKeepClearAreasChangeCallback,
- PIP_KEEP_CLEAR_AREAS_DELAY);
+ mMainExecutor.removeCallbacks(
+ mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.executeDelayed(
+ mMovePipInResponseToKeepClearAreasChangeCallback,
+ PIP_KEEP_CLEAR_AREAS_DELAY);
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
- restricted, unrestricted);
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+ restricted, unrestricted);
}
}
};
@@ -660,25 +642,9 @@
// there's a keyguard present
return;
}
- int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChangedUncheck(mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
- int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- if (!mEnablePipKeepClearAlgorithm) {
- // offset PiP to adjust for bottom inset change
- int pipTop = mPipBoundsState.getBounds().top;
- int diff = newMaxMovementBound - oldMaxMovementBound;
- if (diff < 0 && pipTop > newMaxMovementBound) {
- // bottom inset has increased, move PiP up if it is too low
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
- newMaxMovementBound - pipTop);
- }
- if (diff > 0 && oldMaxMovementBound == pipTop) {
- // bottom inset has decreased, move PiP down if it was by the edge
- mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
- }
- }
}
});
@@ -947,14 +913,8 @@
* Sets both shelf visibility and its height.
*/
private void setShelfHeight(boolean visible, int height) {
- if (mEnablePipKeepClearAlgorithm) {
- // turn this into Launcher keep clear area registration instead
- setLauncherKeepClearAreaHeight(visible, height);
- return;
- }
- if (!mIsKeyguardShowingOrAnimating) {
- setShelfHeightLocked(visible, height);
- }
+ // turn this into Launcher keep clear area registration instead
+ setLauncherKeepClearAreaHeight(visible, height);
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
@@ -1015,16 +975,10 @@
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
-
- if (mEnablePipKeepClearAlgorithm) {
- // preemptively add the keep clear area for Hotseat, so that it is taken into account
- // when calculating the entry destination bounds of PiP window
- mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
- hotseatKeepClearArea);
- } else {
- int shelfHeight = hotseatKeepClearArea.height();
- setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
- }
+ // preemptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
+ hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 3e95498a..9f7dee7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -34,7 +34,6 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Size;
import android.view.DisplayCutout;
@@ -73,14 +72,6 @@
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
- private boolean mEnablePipKeepClearAlgorithm =
- SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
-
- @VisibleForTesting
- void setEnablePipKeepClearAlgorithm(boolean value) {
- mEnablePipKeepClearAlgorithm = value;
- }
-
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
private final Context mContext;
@@ -430,48 +421,6 @@
mIsImeShowing ? mImeOffset : 0,
!mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
- // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
- // occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
- // Defer the update of the current movement bounds until after the user finishes
- // touching the screen
- } else if (mEnablePipKeepClearAlgorithm) {
- // Ignore moving PiP if keep clear algorithm is enabled, since IME and shelf height
- // now are accounted for in the keep clear algorithm calculations
- } else {
- final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
- final Rect toMovementBounds = new Rect();
- mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
- toMovementBounds, mIsImeShowing ? mImeHeight : 0);
- final int prevBottom = mPipBoundsState.getMovementBounds().bottom
- - mMovementBoundsExtraOffsets;
- // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
- // case
- final int toBottom = toMovementBounds.bottom < toMovementBounds.top
- ? toMovementBounds.bottom
- : toMovementBounds.bottom - extraOffset;
-
- if (isExpanded) {
- curBounds.set(mPipBoundsState.getExpandedBounds());
- mPipBoundsAlgorithm.getSnapAlgorithm().applySnapFraction(curBounds,
- toMovementBounds, mSavedSnapFraction);
- }
-
- if (prevBottom < toBottom) {
- // The movement bounds are expanding
- if (curBounds.top > prevBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- } else if (prevBottom > toBottom) {
- // The movement bounds are shrinking
- if (curBounds.top > toBottom - mBottomOffsetBufferPx) {
- mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
- }
- }
- }
- }
-
// Update the movement bounds after doing the calculations based on the old movement bounds
// above
mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2be7a49..29fff03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -71,6 +71,7 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -200,6 +201,19 @@
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
+ mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ if (visible) {
+ DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
+ if (decor != null && DesktopModeStatus.isActive(mContext)
+ && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
+ mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ }
+ }
+ }
+ });
}
@Override
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
index 610cede..fa723e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
@@ -23,6 +23,7 @@
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ArtifactSaverRule
import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.flicker.rules.LaunchAppRule
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
@@ -33,9 +34,10 @@
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
- return RuleChain.outerRule(UnlockScreenRule())
+ return RuleChain.outerRule(ArtifactSaverRule())
+ .around(UnlockScreenRule())
.around(
- NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+ NavigationModeRule(navigationMode.value, false)
)
.around(
LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 0ae2908..05c6ba9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -76,7 +76,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -329,21 +328,7 @@
}
@Test
- public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
- mPipController.setEnablePipKeepClearAlgorithm(false);
- final int displayId = 1;
- final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
-
- mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
- displayId, Set.of(keepClearArea), Set.of());
-
- verify(mMockPipBoundsState, never()).setKeepClearAreas(Mockito.anySet(), Mockito.anySet());
- }
-
- @Test
- public void onKeepClearAreasChanged_featureEnabled_updatesPipBoundsState() {
- mPipController.setEnablePipKeepClearAlgorithm(true);
+ public void onKeepClearAreasChanged_updatesPipBoundsState() {
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 852183c..f65d7af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -174,16 +173,4 @@
verify(mPipResizeGestureHandler, times(1))
.updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
}
-
- @Test
- public void updateMovementBounds_withImeAdjustment_movesPip() {
- mPipTouchHandler.setEnablePipKeepClearAlgorithm(false);
- mFromImeAdjustment = true;
- mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
-
- mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
- mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
-
- verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt());
- }
}
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index eb5878d..8c180da 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -54,6 +54,8 @@
mImpl->updateChildren(std::move(updateFn));
}
+ void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); }
+
[[nodiscard]] explicit operator bool() const {
return mImpl.get() != nullptr;
}
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 1dd22cf..eee93c1 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -109,6 +109,13 @@
output << std::endl;
}
+void RenderNode::visit(std::function<void(const RenderNode&)> func) const {
+ func(*this);
+ if (mDisplayList) {
+ mDisplayList.visit(func);
+ }
+}
+
int RenderNode::getUsageSize() {
int size = sizeof(RenderNode);
size += mStagingDisplayList.getUsedSize();
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index d1e04ad..529a49e9 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -114,7 +114,7 @@
return mDisplayList.containsProjectionReceiver();
}
- const char* getName() const { return mName.string(); }
+ const char* getName() const { return mName.c_str(); }
void setName(const char* name) {
if (name) {
@@ -129,10 +129,6 @@
StretchMask& getStretchMask() { return mStretchMask; }
- VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
-
- void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
-
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -215,6 +211,8 @@
void output(std::ostream& output, uint32_t level);
+ void visit(std::function<void(const RenderNode&)>) const;
+
void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; }
UsageHint usageHint() const { return mUsageHint; }
@@ -222,6 +220,7 @@
int64_t uniqueId() const { return mUniqueId; }
void setIsTextureView() { mIsTextureView = true; }
+ bool isTextureView() const { return mIsTextureView; }
void markDrawStart(SkCanvas& canvas);
void markDrawEnd(SkCanvas& canvas);
@@ -248,7 +247,6 @@
const int64_t mUniqueId;
String8 mName;
- sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index af2d3b3..5c8285a 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -66,6 +66,12 @@
}
}
+void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const {
+ for (auto& child : mChildNodes) {
+ child.getRenderNode()->visit(func);
+ }
+}
+
static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
Vector3 {bounds.fRight, bounds.fTop, 0},
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 7af31a4..e5bd5c9 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -145,6 +145,8 @@
*/
void updateChildren(std::function<void(RenderNode*)> updateFn);
+ void visit(std::function<void(const RenderNode&)> func) const;
+
/**
* Returns true if there is a child render node that is a projection receiver.
*/
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 3d77877..6679f8f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -201,7 +201,7 @@
String8 cachesOutput;
mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
&mRenderThread.renderState());
- ALOGE("%s", cachesOutput.string());
+ ALOGE("%s", cachesOutput.c_str());
if (errorHandler) {
std::ostringstream err;
err << "Unable to create layer for " << node->getName();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 2e0de3f..4064bb9 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -1132,6 +1132,12 @@
return self->mColorMode != ColorMode::Default;
}
+void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const {
+ for (auto node : mRenderNodes) {
+ node->visit(func);
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3f02674..241f8dd 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -238,6 +238,8 @@
static bool shouldDither();
+ void visitAllRenderNodes(std::function<void(const RenderNode&)>) const;
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index eb28c08..94ed06c 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -357,7 +357,15 @@
String8 cachesOutput;
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
- dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.string());
+ dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str());
+ for (auto&& context : mCacheManager->mCanvasContexts) {
+ context->visitAllRenderNodes([&](const RenderNode& node) {
+ if (node.isTextureView()) {
+ dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight());
+ }
+ });
+ }
+ dprintf(fd, "\n");
}
void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index d778feb..d6b7ecf 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -208,3 +208,22 @@
],
min_sdk_version: "24",
}
+
+//##########################################################
+// Variant: Add apk to an apex
+android_app {
+ name: "CtsShimAddApkToApex",
+ sdk_version: "current",
+ srcs: ["shim_add_apk_to_apex/src/android/addapktoapex/app/AddApkToApexDeviceActivity.java"],
+ optimize: {
+ enabled: false,
+ },
+ dex_preopt: {
+ enabled: false,
+ },
+ manifest: "shim_add_apk_to_apex/AndroidManifestAddApkToApex.xml",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.apex.cts.shim.v2_add_apk_to_apex",
+ ],
+}
diff --git a/packages/CtsShim/build/shim_add_apk_to_apex/AndroidManifestAddApkToApex.xml b/packages/CtsShim/build/shim_add_apk_to_apex/AndroidManifestAddApkToApex.xml
new file mode 100644
index 0000000..0e620b0
--- /dev/null
+++ b/packages/CtsShim/build/shim_add_apk_to_apex/AndroidManifestAddApkToApex.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.addapktoapex.app">
+
+ <application>
+ <activity android:name=".AddApkToApexDeviceActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/CtsShim/build/shim_add_apk_to_apex/src/android/addapktoapex/app/AddApkToApexDeviceActivity.java b/packages/CtsShim/build/shim_add_apk_to_apex/src/android/addapktoapex/app/AddApkToApexDeviceActivity.java
new file mode 100644
index 0000000..c68904b
--- /dev/null
+++ b/packages/CtsShim/build/shim_add_apk_to_apex/src/android/addapktoapex/app/AddApkToApexDeviceActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.addapktoapex.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A simple activity which logs to Logcat.
+ */
+public class AddApkToApexDeviceActivity extends Activity {
+
+ private static final String TAG = AddApkToApexDeviceActivity.class.getSimpleName();
+
+ /**
+ * The test string to log.
+ */
+ private static final String TEST_STRING = "AddApkToApexTestString";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ // Log the test string to Logcat.
+ Log.i(TAG, TEST_STRING);
+ }
+
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index f2f019d9..736e0ef 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -34,7 +34,6 @@
import android.os.Bundle;
import android.os.Process;
import android.os.UserManager;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -51,12 +50,10 @@
public class InstallStart extends Activity {
private static final String TAG = InstallStart.class.getSimpleName();
-
- private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = 1;
- private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = 2;
private PackageManager mPackageManager;
private UserManager mUserManager;
private boolean mAbortInstall = false;
+ private boolean mShouldFinish = true;
private final boolean mLocalLOGV = false;
@@ -131,7 +128,7 @@
mAbortInstall = true;
}
- checkDevicePolicyRestriction();
+ checkDevicePolicyRestrictions();
final String installerPackageNameFromIntent = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
@@ -150,7 +147,9 @@
if (mAbortInstall) {
setResult(RESULT_CANCELED);
- finish();
+ if (mShouldFinish) {
+ finish();
+ }
return;
}
@@ -281,58 +280,52 @@
return originatingUid == installerUid;
}
- private void checkDevicePolicyRestriction() {
- // Check for install apps user restriction first.
- final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
- UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
- if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
- if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
- mAbortInstall = true;
- showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
- return;
- } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
- if (mLocalLOGV) {
- Log.i(TAG, "install not allowed by admin; showing "
- + Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
- }
- mAbortInstall = true;
- startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
- return;
- }
+ private void checkDevicePolicyRestrictions() {
+ final String[] restrictions = new String[] {
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ };
- final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
- final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
- final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
- & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
- if (systemRestriction != 0) {
- if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
+ final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ for (String restriction : restrictions) {
+ if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+ continue;
+ }
+
mAbortInstall = true;
- showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
- } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
- mAbortInstall = true;
- startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
- } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
- mAbortInstall = true;
- startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+
+ // If the given restriction is set by an admin, display information about the
+ // admin enforcing the restriction for the affected user. If not enforced by the admin,
+ // show the system dialog.
+ final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
+ if (showAdminSupportDetailsIntent != null) {
+ if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
+ startActivity(showAdminSupportDetailsIntent);
+ } else {
+ if (mLocalLOGV) Log.i(TAG, "Restriction set by system: " + restriction);
+ mShouldFinish = false;
+ showDialogInner(restriction);
+ }
+ break;
}
}
/**
- * Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
+ * Replace any dialog shown by the dialog with the one for the given
+ * {@link #createDialog(String)}.
*
- * @param id The dialog type to add
+ * @param restriction The restriction to create the dialog for
*/
- private void showDialogInner(int id) {
- if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + id + ")");
+ private void showDialogInner(String restriction) {
+ if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + restriction + ")");
DialogFragment currentDialog =
(DialogFragment) getFragmentManager().findFragmentByTag("dialog");
if (currentDialog != null) {
currentDialog.dismissAllowingStateLoss();
}
- DialogFragment newDialog = createDialog(id);
+ DialogFragment newDialog = createDialog(restriction);
if (newDialog != null) {
getFragmentManager().beginTransaction()
.add(newDialog, "dialog").commitAllowingStateLoss();
@@ -342,34 +335,20 @@
/**
* Create a new dialog.
*
- * @param id The id of the dialog (determines dialog type)
+ * @param restriction The restriction to create the dialog for
* @return The dialog
*/
- private DialogFragment createDialog(int id) {
- if (mLocalLOGV) Log.i(TAG, "createDialog(" + id + ")");
- switch (id) {
- case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
+ private DialogFragment createDialog(String restriction) {
+ if (mLocalLOGV) Log.i(TAG, "createDialog(" + restriction + ")");
+ switch (restriction) {
+ case UserManager.DISALLOW_INSTALL_APPS:
return PackageUtil.SimpleErrorDialog.newInstance(
R.string.install_apps_user_restriction_dlg_text);
- case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY:
return PackageUtil.SimpleErrorDialog.newInstance(
R.string.unknown_apps_user_restriction_dlg_text);
}
return null;
}
-
- private void startAdminSupportDetailsActivity(String restriction) {
- if (mLocalLOGV) Log.i(TAG, "startAdminSupportDetailsActivity(): " + restriction);
-
- // If the given restriction is set by an admin, display information about the
- // admin enforcing the restriction for the affected user.
- final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
- final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction);
- if (showAdminSupportDetailsIntent != null) {
- if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent);
- startActivity(showAdminSupportDetailsIntent);
- } else {
- if (mLocalLOGV) Log.w(TAG, "not intent for " + restriction);
- }
- }
}
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index 0b74fa8..d1bcb57 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -2,7 +2,7 @@
flag {
name: "use_media_router2_for_info_media_manager"
- namespace: "placeholder_namespace"
+ namespace: "media_solutions"
description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
bug: "192657812"
}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 9ab84d2..f90a17a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -45,6 +45,7 @@
private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+ public static final int BATTERY_LEVEL_UNKNOWN = -1;
public static final int CHARGING_UNKNOWN = -1;
public static final int CHARGING_SLOWLY = 0;
public static final int CHARGING_REGULAR = 1;
@@ -186,12 +187,13 @@
/** Gets the battery level from the intent. */
public static int getBatteryLevel(Intent batteryChangedIntent) {
if (batteryChangedIntent == null) {
- return -1; /*invalid battery level*/
+ return BATTERY_LEVEL_UNKNOWN;
}
- final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ final int level =
+ batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
return scale == 0
- ? -1 /*invalid battery level*/
+ ? BATTERY_LEVEL_UNKNOWN
: Math.round((level / (float) scale) * 100f);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 423c8a3..2d62e2a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -242,6 +242,7 @@
Settings.Secure.HEARING_AID_CALL_ROUTING,
Settings.Secure.HEARING_AID_MEDIA_ROUTING,
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
- Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED
+ Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+ Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6c9da97..4494765 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -200,6 +200,7 @@
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1192e00..d2b444b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1926,6 +1926,9 @@
dumpSetting(s, p,
Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
+ SecureSettingsProto.Assist.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED);
p.end(assistToken);
final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 798bdec4..ab0225d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -409,6 +409,18 @@
scope.launch(bgDispatcher) { mutateSetting { it.copy(seedColor = value) } }
}
+ // Returns currentClockId if clock is connected, otherwise DEFAULT_CLOCK_ID. Since this
+ // is dependent on which clocks are connected, it may change when a clock is installed or
+ // removed from the device (unlike currentClockId).
+ // TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors.
+ val activeClockId: String
+ get() {
+ if (!availableClocks.containsKey(currentClockId)) {
+ return DEFAULT_CLOCK_ID
+ }
+ return currentClockId
+ }
+
init {
// Register default clock designs
for (clock in defaultClockProvider.getClocks()) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e7a53e5b..b28920c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -65,7 +65,7 @@
protected var onSecondaryDisplay: Boolean = false
override val events: DefaultClockEvents
- override val config = ClockConfig()
+ override val config = ClockConfig(DEFAULT_CLOCK_ID)
init {
val parent = FrameLayout(ctx)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 527f8007..e2f4793 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -199,6 +199,8 @@
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
+ val id: String,
+
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
diff --git a/packages/SystemUI/res/drawable/auth_credential_emergency_button_background.xml b/packages/SystemUI/res/drawable/auth_credential_emergency_button_background.xml
new file mode 100644
index 0000000..85450b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/auth_credential_emergency_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners android:radius="25dp"/>
+ <solid android:color="@android:color/system_accent3_100" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index 4a9d41f..b83f15a 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,6 +14,4 @@
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp"
android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index e2ce34f..e439f77 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -61,29 +61,46 @@
</RelativeLayout>
- <LinearLayout
+ <FrameLayout
android:id="@+id/auth_credential_input"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- style="?passwordTextAppearance"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- android:inputType="textPassword"
- android:minHeight="48dp" />
-
- <TextView
- android:id="@+id/error"
- style="?errorTextAppearance"
- android:layout_gravity="center"
+ <LinearLayout
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|top"
+ android:orientation="vertical">
- </LinearLayout>
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="?passwordTextAppearance"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp"/>
+
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ style="@style/AuthCredentialEmergencyButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:text="@string/work_challenge_emergency_button_text"/>
+ </FrameLayout>
</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 88f138f..d5af377 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -60,27 +60,44 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearanceLand"
+ android:layout_below="@id/description"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
</RelativeLayout>
- <FrameLayout
+ <RelativeLayout
android:layout_weight="1"
- style="?containerStyle"
android:layout_width="0dp"
android:layout_height="match_parent">
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_gravity="center"
- android:layout_width="@dimen/biometric_auth_pattern_view_size"
- android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
-
- <TextView
- android:id="@+id/error"
- style="?errorTextAppearance"
+ <FrameLayout
+ style="?containerStyle"
+ android:layout_above="@id/emergencyCallButton"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom"/>
+ android:layout_height="match_parent">
- </FrameLayout>
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
+ </FrameLayout>
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ style="@style/AuthCredentialEmergencyButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="35dp"
+ android:visibility="gone"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:text="@string/work_challenge_emergency_button_text"/>
+ </RelativeLayout>
</com.android.systemui.biometrics.ui.CredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 33f1b10..9336845 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -65,29 +65,46 @@
</ScrollView>
- <LinearLayout
+ <FrameLayout
android:id="@+id/auth_credential_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- style="?passwordTextAppearance"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- android:inputType="textPassword"
- android:minHeight="48dp" />
-
- <TextView
- android:id="@+id/error"
- style="?errorTextAppearance"
- android:layout_gravity="center_horizontal"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|top"
+ android:orientation="vertical">
- </LinearLayout>
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="?passwordTextAppearance"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp"/>
+
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ style="@style/AuthCredentialEmergencyButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="12dp"
+ android:text="@string/work_challenge_emergency_button_text"/>
+ </FrameLayout>
</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 81ca3718..59828fd 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -58,24 +58,42 @@
android:layout_height="wrap_content"/>
</RelativeLayout>
- <FrameLayout
+ <RelativeLayout
android:id="@+id/auth_credential_container"
- style="?containerStyle"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_gravity="center"
- android:layout_width="@dimen/biometric_auth_pattern_view_size"
- android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
+ <FrameLayout
+ android:layout_centerInParent="true"
+ android:layout_above="@id/emergencyCallButton"
+ style="?containerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
- <TextView
- android:id="@+id/error"
- style="?errorTextAppearance"
- android:layout_width="match_parent"
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/biometric_auth_pattern_view_size"
+ android:layout_height="@dimen/biometric_auth_pattern_view_size"/>
+
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"/>
+ </FrameLayout>
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ style="@style/AuthCredentialEmergencyButtonStyle"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom"/>
- </FrameLayout>
+ android:layout_alignParentBottom="true"
+ android:visibility="gone"
+ android:layout_marginBottom="35dp"
+ android:layout_centerHorizontal="true"
+ android:text="@string/work_challenge_emergency_button_text"/>
+ </RelativeLayout>
</com.android.systemui.biometrics.ui.CredentialPatternView>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
index 78cd718..39ec09b 100644
--- a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -34,8 +34,8 @@
android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
android:contentDescription="@string/screenshot_dismiss_work_profile">
<ImageView
- android:layout_width="16dp"
- android:layout_height="16dp"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
android:layout_gravity="center"
android:background="@drawable/circular_background"
android:backgroundTint="?androidprv:attr/materialColorSurfaceContainerHigh"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index d693631..8bc3eed 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -221,6 +221,7 @@
<attr name="descriptionTextAppearance" format="reference" />
<attr name="passwordTextAppearance" format="reference" />
<attr name="errorTextAppearance" format="reference"/>
+ <attr name="errorTextAppearanceLand" format="reference"/>
</declare-styleable>
<declare-styleable name="LogAccessPermissionGrantDialog">
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cddfda2..2003fa3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -386,6 +386,8 @@
<string name="biometric_dialog_wrong_password">Wrong password</string>
<!-- Error string shown when the user enters too many incorrect attempts [CHAR LIMIT=120]-->
<string name="biometric_dialog_credential_too_many_attempts">Too many incorrect attempts.\nTry again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+ <!-- Button text shown on an authentication screen giving the user the option to make an emergency call without unlocking their device [CHAR LIMIT=20] -->
+ <string name="work_challenge_emergency_button_text">Emergency</string>
<!-- Error string shown when the user enters an incorrect PIN/pattern/password and it counts towards the max attempts before the data on the device is wiped. [CHAR LIMIT=NONE]-->
<string name="biometric_dialog_credential_attempts_before_wipe">Try again. Attempt <xliff:g id="attempts" example="1">%1$d</xliff:g> of <xliff:g id="max_attempts" example="3">%2$d</xliff:g>.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 10340c6..6991b96 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -236,6 +236,13 @@
<item name="android:gravity">center</item>
</style>
+ <style name="TextAppearance.AuthNonBioCredential.ErrorLand">
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/colorError</item>
+ <item name="android:gravity">start</item>
+ </style>
+
<style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:gravity">center</item>
<item name="android:paddingTop">28dp</item>
@@ -276,6 +283,17 @@
<item name="android:minWidth">200dp</item>
</style>
+ <style name="AuthCredentialEmergencyButtonStyle">
+ <item name="android:background">@drawable/auth_credential_emergency_button_background</item>
+ <item name="android:textColor">@android:color/system_accent3_900</item>
+ <item name="android:outlineProvider">none</item>
+ <item name="android:paddingTop">15dp</item>
+ <item name="android:paddingBottom">15dp</item>
+ <item name="android:paddingLeft">30dp</item>
+ <item name="android:paddingRight">30dp</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
@@ -353,6 +371,7 @@
<item name="descriptionTextAppearance">@style/TextAppearance.AuthNonBioCredential.Description</item>
<item name="passwordTextAppearance">@style/TextAppearance.AuthCredential.PasswordEntry</item>
<item name="errorTextAppearance">@style/TextAppearance.AuthNonBioCredential.Error</item>
+ <item name="errorTextAppearanceLand">@style/TextAppearance.AuthNonBioCredential.ErrorLand</item>
</style>
<style name="LockPatternViewStyle" >
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index b589887..06b6692 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -39,6 +39,7 @@
public class KeyguardClockSwitch extends RelativeLayout {
private static final String TAG = "KeyguardClockSwitch";
+ public static final String MISSING_CLOCK_ID = "CLOCK_MISSING";
private static final long CLOCK_OUT_MILLIS = 133;
private static final long CLOCK_IN_MILLIS = 167;
@@ -196,6 +197,14 @@
return mLogBuffer;
}
+ /** Returns the id of the currently rendering clock */
+ public String getClockId() {
+ if (mClock == null) {
+ return MISSING_CLOCK_ID;
+ }
+ return mClock.getConfig().getId();
+ }
+
void setClock(ClockController clock, int statusBarState) {
mClock = clock;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index dd39f1d..05ace74 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -222,8 +222,10 @@
mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
- mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous clocks
- mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+ if (!mOnlyClock) {
+ mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+ }
if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 04692c4..9f3908a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
import android.app.ActivityManager;
@@ -370,8 +371,12 @@
@Override
public void onOrientationChanged(int orientation) {
- KeyguardSecurityContainerController.this
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ // TODO(b/295603468)
+ // Fix reinflation of views when flag is enabled.
+ KeyguardSecurityContainerController.this
.onDensityOrFontScaleOrOrientationChanged();
+ }
}
};
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index caebc30..a3ee220 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -16,6 +16,7 @@
val description: String,
val userInfo: BiometricUserInfo,
val operationInfo: BiometricOperationInfo,
+ val showEmergencyCallButton: Boolean,
) {
/** Prompt using one or more biometrics. */
class Biometric(
@@ -29,7 +30,8 @@
subtitle = info.subtitle?.toString() ?: "",
description = info.description?.toString() ?: "",
userInfo = userInfo,
- operationInfo = operationInfo
+ operationInfo = operationInfo,
+ showEmergencyCallButton = info.isShowEmergencyCallButton
) {
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
}
@@ -46,6 +48,7 @@
description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "",
userInfo = userInfo,
operationInfo = operationInfo,
+ showEmergencyCallButton = info.isShowEmergencyCallButton
) {
/** PIN prompt. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 9292bd7..4ac9f96 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -2,6 +2,7 @@
import android.view.View
import android.view.ViewGroup
+import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.Lifecycle
@@ -46,6 +47,7 @@
val descriptionView: TextView = view.requireViewById(R.id.description)
val iconView: ImageView? = view.findViewById(R.id.icon)
val errorView: TextView = view.requireViewById(R.id.error)
+ val emergencyButtonView: Button = view.requireViewById(R.id.emergencyCallButton)
var errorTimer: Job? = null
@@ -75,6 +77,13 @@
iconView?.setImageDrawable(header.icon)
+ if (header.showEmergencyCallButton) {
+ emergencyButtonView.visibility = View.VISIBLE
+ emergencyButtonView.setOnClickListener {
+ viewModel.doEmergencyCall(view.context)
+ }
+ }
+
// Only animate this if we're transitioning from a biometric view.
if (viewModel.animateContents.value) {
view.animateCredentialViewIn()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
index 3257f20..c6d9085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
@@ -10,4 +10,5 @@
val subtitle: String
val description: String
val icon: Drawable
+ val showEmergencyCallButton: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index a3b23ca..6212ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -41,6 +41,7 @@
subtitle = request.subtitle,
description = request.description,
icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
+ showEmergencyCallButton = request.showEmergencyCallButton
)
}
@@ -136,6 +137,18 @@
}
}
}
+
+ fun doEmergencyCall(context: Context) {
+ val intent =
+ context
+ .getSystemService(android.telecom.TelecomManager::class.java)!!
+ .createLaunchEmergencyDialerIntent(null)
+ .setFlags(
+ android.content.Intent.FLAG_ACTIVITY_NEW_TASK or
+ android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
+ )
+ context.startActivity(intent)
+ }
}
private fun Context.asBadCredentialErrorMessage(prompt: BiometricPromptRequest?): String =
@@ -174,6 +187,7 @@
override val subtitle: String,
override val description: String,
override val icon: Drawable,
+ override val showEmergencyCallButton: Boolean,
) : CredentialHeaderViewModel
private fun CredentialHeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 970d00b..3bf9cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -107,6 +107,7 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LetterboxModule;
+import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -185,6 +186,7 @@
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
+ NotificationIconAreaControllerModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f3900ac..1516f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -355,7 +355,7 @@
// TODO(b/278068252): Tracking Bug
@JvmField
- val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = false)
+ val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = true)
// TODO(b/254512383): Tracking Bug
@JvmField
@@ -512,11 +512,6 @@
val ENABLE_FLING_TO_DISMISS_PIP =
sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_pip", default = true)
- @Keep
- @JvmField
- val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- sysPropBooleanFlag("persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
@@ -628,6 +623,10 @@
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
+ /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
+ @JvmField
+ val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 562c4db..ed84884 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -40,10 +40,10 @@
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -53,7 +53,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -68,18 +67,6 @@
bouncerRepository: KeyguardBouncerRepository,
configurationRepository: ConfigurationRepository,
) {
-
- data class PreviewMode(
- val isInPreviewMode: Boolean = false,
- val shouldHighlightSelectedAffordance: Boolean = false,
- )
-
- /**
- * Whether this view-model instance is powering the preview experience that renders exclusively
- * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
- * experience.
- */
- val previewMode = MutableStateFlow(PreviewMode())
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
* all.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index 635961b..e501ece 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -54,7 +54,11 @@
if (event.handleAction()) {
when (event.keyCode) {
KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
- KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
+ KeyEvent.KEYCODE_SPACE,
+ KeyEvent.KEYCODE_ENTER ->
+ if (isDeviceInteractive()) {
+ return collapseShadeLockedOrShowPrimaryBouncer()
+ }
}
}
return false
@@ -90,16 +94,22 @@
(statusBarStateController.state != StatusBarState.SHADE) &&
statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
if (shouldUnlockOnMenuPressed) {
- shadeController.animateCollapseShadeForced()
- return true
+ return collapseShadeLockedOrShowPrimaryBouncer()
}
return false
}
- private fun dispatchSpaceEvent(): Boolean {
- if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
- shadeController.animateCollapseShadeForced()
- return true
+ private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
+ when (statusBarStateController.state) {
+ StatusBarState.SHADE -> return false
+ StatusBarState.SHADE_LOCKED -> {
+ shadeController.animateCollapseShadeForced()
+ return true
+ }
+ StatusBarState.KEYGUARD -> {
+ statusBarKeyguardViewManager.showPrimaryBouncer(true)
+ return true
+ }
}
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index dd3da97..41c1c96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
@@ -56,25 +57,31 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.monet.ColorScheme
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.DefaultClockController
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
/** Renders the preview of the lock screen. */
class KeyguardPreviewRenderer
+@OptIn(ExperimentalCoroutinesApi::class)
@AssistedInject
constructor(
@Application private val context: Context,
@@ -99,6 +106,9 @@
@Assisted bundle: Bundle,
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+ private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+ private val chipbarCoordinator: ChipbarCoordinator,
+ private val keyguardStateController: KeyguardStateController,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -130,19 +140,21 @@
init {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- keyguardRootViewModel.enablePreviewMode(
+ keyguardRootViewModel.enablePreviewMode()
+ quickAffordancesCombinedViewModel.enablePreviewMode(
initiallySelectedSlotId =
- bundle.getString(
- KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
- ),
+ bundle.getString(
+ KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+ )
+ ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
} else {
bottomAreaViewModel.enablePreviewMode(
initiallySelectedSlotId =
- bundle.getString(
- KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
- ),
+ bundle.getString(
+ KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+ ),
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
}
@@ -163,17 +175,8 @@
val rootView = FrameLayout(context)
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- val keyguardRootView = KeyguardRootView(context, null)
- rootView.addView(
- keyguardRootView,
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- ),
- )
- setupShortcuts(keyguardRootView)
- KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
- keyguardBlueprintInteractor.refreshBlueprint()
+ setupKeyguardRootView(rootView)
+ setupShortcuts(rootView)
} else {
setUpBottomArea(rootView)
}
@@ -314,7 +317,7 @@
false,
) as KeyguardBottomAreaView
bottomAreaView.init(
- viewModel = bottomAreaViewModel,
+ viewModel = bottomAreaViewModel,
)
parentView.addView(
bottomAreaView,
@@ -325,10 +328,34 @@
)
}
- private fun setupShortcuts(keyguardRootView: KeyguardRootView) {
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun setupKeyguardRootView(rootView: FrameLayout) {
+ val keyguardRootView = KeyguardRootView(context, null)
+ disposables.add(
+ KeyguardRootViewBinder.bind(
+ keyguardRootView,
+ keyguardRootViewModel,
+ featureFlags,
+ occludingAppDeviceEntryMessageViewModel,
+ chipbarCoordinator,
+ keyguardStateController,
+ )
+ )
+ rootView.addView(
+ keyguardRootView,
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ ),
+ )
+ KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+ keyguardBlueprintInteractor.refreshBlueprint()
+ }
+
+ private fun setupShortcuts(rootView: FrameLayout) {
shortcutsBindings.add(
KeyguardQuickAffordanceViewBinder.bind(
- keyguardRootView.requireViewById(R.id.start_button),
+ rootView.requireViewById(R.id.start_button),
quickAffordancesCombinedViewModel.startButton,
keyguardRootViewModel.alpha,
falsingManager,
@@ -340,7 +367,7 @@
shortcutsBindings.add(
KeyguardQuickAffordanceViewBinder.bind(
- keyguardRootView.requireViewById(R.id.end_button),
+ rootView.requireViewById(R.id.end_button),
quickAffordancesCombinedViewModel.endButton,
keyguardRootViewModel.alpha,
falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 56a98455..02ea550 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,22 +18,20 @@
package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.VisibleForTesting
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardQuickAffordancesCombinedViewModel
@@ -43,6 +41,18 @@
private val keyguardInteractor: KeyguardInteractor,
) {
+ data class PreviewMode(
+ val isInPreviewMode: Boolean = false,
+ val shouldHighlightSelectedAffordance: Boolean = false,
+ )
+
+ /**
+ * Whether this view-model instance is powering the preview experience that renders exclusively
+ * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+ * experience.
+ */
+ private val previewMode = MutableStateFlow(PreviewMode())
+
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
* wallpaper picker application. This is ignored for the actual, real lock screen experience.
@@ -85,39 +95,63 @@
selectedPreviewSlotId.value = slotId
}
+ /**
+ * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
+ * the lock screen preview in wallpaper picker / settings and not the real experience on the
+ * lock screen.
+ *
+ * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+ * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
+ * highlighted (while all others are dimmed to make the selected one stand out).
+ */
+ fun enablePreviewMode(
+ initiallySelectedSlotId: String?,
+ shouldHighlightSelectedAffordance: Boolean,
+ ) {
+ val newPreviewMode =
+ PreviewMode(
+ isInPreviewMode = true,
+ shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+ )
+ onPreviewSlotSelected(
+ initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ )
+ previewMode.value = newPreviewMode
+ }
+
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return keyguardInteractor.previewMode.flatMapLatest { previewMode ->
+ return previewMode.flatMapLatest { previewMode ->
combine(
- if (previewMode.isInPreviewMode) {
- quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
- } else {
- quickAffordanceInteractor.quickAffordance(position = position)
- },
- keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
- areQuickAffordancesFullyOpaque,
- selectedPreviewSlotId,
- quickAffordanceInteractor.useLongPress(),
- ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
- val slotId = position.toSlotId()
- val isSelected = selectedPreviewSlotId == slotId
- model.toViewModel(
- animateReveal = !previewMode.isInPreviewMode && animateReveal,
- isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
- isSelected =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- isSelected,
- isDimmed =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- !isSelected,
- forceInactive = previewMode.isInPreviewMode,
- slotId = slotId,
- useLongPress = useLongPress,
- )
- }
+ if (previewMode.isInPreviewMode) {
+ quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
+ } else {
+ quickAffordanceInteractor.quickAffordance(position = position)
+ },
+ keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
+ areQuickAffordancesFullyOpaque,
+ selectedPreviewSlotId,
+ quickAffordanceInteractor.useLongPress(),
+ ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
+ val slotId = position.toSlotId()
+ val isSelected = selectedPreviewSlotId == slotId
+ model.toViewModel(
+ animateReveal = !previewMode.isInPreviewMode && animateReveal,
+ isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
+ isSelected =
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ isSelected,
+ isDimmed =
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ !isSelected,
+ forceInactive = previewMode.isInPreviewMode,
+ slotId = slotId,
+ useLongPress = useLongPress,
+ )
+ }
.distinctUntilChanged()
}
}
@@ -167,8 +201,6 @@
// time, we don't want the number to be too close to 1.0 such that there is a chance that we
// never treat the affordance UI as "fully opaque" as that would risk making it forever not
// clickable.
- @VisibleForTesting
- const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
+ @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
}
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 316ca77..92b9ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -19,29 +19,37 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardRootViewModel
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
- private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-)
-{
+) {
+
+ data class PreviewMode(val isInPreviewMode: Boolean = false)
+
+ /**
+ * Whether this view-model instance is powering the preview experience that renders exclusively
+ * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+ * experience.
+ */
+ private val previewMode = MutableStateFlow(PreviewMode())
+
/** Represents the current state of the KeyguardRootView visibility */
val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
keyguardInteractor.keyguardRootViewVisibilityState
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
- keyguardInteractor.previewMode.flatMapLatest {
+ previewMode.flatMapLatest {
if (it.isInPreviewMode) {
flowOf(1f)
} else {
@@ -53,23 +61,9 @@
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
* the lock screen preview in wallpaper picker / settings and not the real experience on the
* lock screen.
- *
- * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
- * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
- * highlighted (while all others are dimmed to make the selected one stand out).
*/
- fun enablePreviewMode(
- initiallySelectedSlotId: String?,
- shouldHighlightSelectedAffordance: Boolean,
- ) {
- keyguardInteractor.previewMode.value =
- KeyguardInteractor.PreviewMode(
- isInPreviewMode = true,
- shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
- )
- keyguardQuickAffordancesCombinedViewModel.onPreviewSlotSelected(
- initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- )
+ fun enablePreviewMode() {
+ val newPreviewMode = PreviewMode(true)
+ previewMode.value = newPreviewMode
}
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index f8f784f..3d4fca1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -216,6 +216,58 @@
}
}
+ private void stopSound(Command cmd) {
+ final MediaPlayer mp;
+ synchronized (mPlayerLock) {
+ mp = mPlayer;
+ mPlayer = null;
+ }
+ if (mp == null) {
+ Log.w(mTag, "STOP command without a player");
+ return;
+ }
+
+ long delay = SystemClock.uptimeMillis() - cmd.requestTime;
+ if (delay > 1000) {
+ Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
+ }
+ try {
+ mp.stop();
+ } catch (Exception e) {
+ Log.w(mTag, "Failed to stop MediaPlayer", e);
+ }
+ if (DEBUG) {
+ Log.i(mTag, "About to release MediaPlayer piid:"
+ + mp.getPlayerIId() + " due to notif cancelled");
+ }
+ try {
+ mp.release();
+ } catch (Exception e) {
+ Log.w(mTag, "Failed to release MediaPlayer", e);
+ }
+ synchronized (mQueueAudioFocusLock) {
+ if (mAudioManagerWithAudioFocus != null) {
+ if (DEBUG) {
+ Log.d(mTag, "in STOP: abandonning AudioFocus");
+ }
+ try {
+ mAudioManagerWithAudioFocus.abandonAudioFocus(null);
+ } catch (Exception e) {
+ Log.w(mTag, "Failed to abandon audio focus", e);
+ }
+ mAudioManagerWithAudioFocus = null;
+ }
+ }
+ synchronized (mCompletionHandlingLock) {
+ if ((mLooper != null) && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+ if (DEBUG) {
+ Log.d(mTag, "in STOP: quitting looper " + mLooper);
+ }
+ mLooper.quit();
+ }
+ }
+ }
+
private final class CmdThread extends java.lang.Thread {
CmdThread() {
super("NotificationPlayer-" + mTag);
@@ -229,62 +281,28 @@
if (DEBUG) Log.d(mTag, "RemoveFirst");
cmd = mCmdQueue.removeFirst();
}
-
- switch (cmd.code) {
- case PLAY:
- if (DEBUG) Log.d(mTag, "PLAY");
- startSound(cmd);
- break;
- case STOP:
- if (DEBUG) Log.d(mTag, "STOP");
- final MediaPlayer mp;
- synchronized (mPlayerLock) {
- mp = mPlayer;
- mPlayer = null;
+ try {
+ switch (cmd.code) {
+ case PLAY:
+ if (DEBUG) Log.d(mTag, "PLAY");
+ startSound(cmd);
+ break;
+ case STOP:
+ if (DEBUG) Log.d(mTag, "STOP");
+ stopSound(cmd);
+ break;
}
- if (mp != null) {
- long delay = SystemClock.uptimeMillis() - cmd.requestTime;
- if (delay > 1000) {
- Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
+ } finally {
+ synchronized (mCmdQueue) {
+ if (mCmdQueue.size() == 0) {
+ // nothing left to do, quit
+ // doing this check after we're done prevents the case where they
+ // added it during the operation from spawning two threads and
+ // trying to do them in parallel.
+ mThread = null;
+ releaseWakeLock();
+ return;
}
- try {
- mp.stop();
- } catch (Exception e) { }
- if (DEBUG) {
- Log.i(mTag, "About to release MediaPlayer piid:"
- + mp.getPlayerIId() + " due to notif cancelled");
- }
- mp.release();
- synchronized(mQueueAudioFocusLock) {
- if (mAudioManagerWithAudioFocus != null) {
- if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
- mAudioManagerWithAudioFocus.abandonAudioFocus(null);
- mAudioManagerWithAudioFocus = null;
- }
- }
- synchronized (mCompletionHandlingLock) {
- if ((mLooper != null) &&
- (mLooper.getThread().getState() != Thread.State.TERMINATED))
- {
- if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
- mLooper.quit();
- }
- }
- } else {
- Log.w(mTag, "STOP command without a player");
- }
- break;
- }
-
- synchronized (mCmdQueue) {
- if (mCmdQueue.size() == 0) {
- // nothing left to do, quit
- // doing this check after we're done prevents the case where they
- // added it during the operation from spawning two threads and
- // trying to do them in parallel.
- mThread = null;
- releaseWakeLock();
- return;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index a3d1d8c..d8824983 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -515,11 +515,13 @@
mSeekBar.setOnTouchListener((v, event) -> false);
updateIconAreaClickListener((v) -> {
if (device.getCurrentVolume() == 0) {
+ mController.logInteractionUnmuteDevice(device);
mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME);
mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
updateUnmutedVolumeIcon();
mIconAreaLayout.setOnTouchListener(((iconV, event) -> false));
} else {
+ mController.logInteractionMuteDevice(device);
mSeekBar.resetVolume();
mController.adjustVolume(device, 0);
updateMutedVolumeIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index d281f50..bb0e9d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -837,6 +837,14 @@
mMetricLogger.logInteractionAdjustVolume(device);
}
+ void logInteractionMuteDevice(MediaDevice device) {
+ mMetricLogger.logInteractionMute(device);
+ }
+
+ void logInteractionUnmuteDevice(MediaDevice device) {
+ mMetricLogger.logInteractionUnmute(device);
+ }
+
String getPackageName() {
return mPackageName;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 412d1a3..ffd626a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -154,6 +154,38 @@
}
/**
+ * Do the metric logging of muting device.
+ */
+ public void logInteractionMute(MediaDevice source) {
+ if (DEBUG) {
+ Log.d(TAG, "logInteraction - Mute");
+ }
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
+ SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__MUTE,
+ getInteractionDeviceType(source),
+ getLoggingPackageName(),
+ source.isSuggestedDevice());
+ }
+
+ /**
+ * Do the metric logging of unmuting device.
+ */
+ public void logInteractionUnmute(MediaDevice source) {
+ if (DEBUG) {
+ Log.d(TAG, "logInteraction - Unmute");
+ }
+
+ SysUiStatsLog.write(
+ SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
+ SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__UNMUTE,
+ getInteractionDeviceType(source),
+ getLoggingPackageName(),
+ source.isSuggestedDevice());
+ }
+
+ /**
* Do the metric logging of content switching failure.
*
* @param deviceItemList media item list for device count updating
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 67927a4..cb87e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -22,13 +22,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.external.TileServiceRequestController
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,10 +36,11 @@
/**
* Adapter to determine what real class to use for classes that depend on [QSHost].
- * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
- * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
- * routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
+ * * When [QSPipelineFlagsRepository.pipelineHostEnabled] is false, all calls will be routed to
* [QSTileHost].
+ * * When [QSPipelineFlagsRepository.pipelineHostEnabled] is true, calls regarding the current set
+ * of tiles will be routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will
+ * still be routed to [QSTileHost].
*
* This routing also includes dumps.
*/
@@ -53,7 +53,7 @@
private val context: Context,
private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
@Application private val scope: CoroutineScope,
- private val featureFlags: FeatureFlags,
+ flags: QSPipelineFlagsRepository,
dumpManager: DumpManager,
) : QSHost {
@@ -61,7 +61,7 @@
private const val TAG = "QSTileHost"
}
- private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
+ private val useNewHost = flags.pipelineHostEnabled
@GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 432147f..e57db56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -35,8 +35,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -50,6 +48,7 @@
import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
@@ -119,7 +118,7 @@
private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
- private final FeatureFlags mFeatureFlags;
+ private final QSPipelineFlagsRepository mFeatureFlags;
@Inject
public QSTileHost(Context context,
@@ -135,7 +134,7 @@
CustomTileStatePersister customTileStatePersister,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager,
- FeatureFlags featureFlags
+ QSPipelineFlagsRepository featureFlags
) {
mContext = context;
mUserContext = context;
@@ -162,7 +161,7 @@
// finishes before creating any tiles.
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
- if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
+ if (!mFeatureFlags.getPipelineAutoAddEnabled()) {
mAutoTiles = autoTiles.get();
}
});
@@ -283,7 +282,7 @@
}
}
// Do not process tiles if the flag is enabled.
- if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ if (mFeatureFlags.getPipelineHostEnabled()) {
return;
}
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 1f63f5d..b2111d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -16,8 +16,6 @@
package com.android.systemui.qs.dagger
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSHostAdapter
import com.android.systemui.qs.QSTileHost
@@ -27,6 +25,7 @@
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -45,11 +44,11 @@
@Provides
@JvmStatic
fun providePanelInteractor(
- featureFlags: FeatureFlags,
+ featureFlags: QSPipelineFlagsRepository,
qsHost: QSTileHost,
panelInteractorImpl: PanelInteractorImpl
): PanelInteractor {
- return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ return if (featureFlags.pipelineHostEnabled) {
panelInteractorImpl
} else {
qsHost
@@ -59,11 +58,11 @@
@Provides
@JvmStatic
fun provideCustomTileAddedRepository(
- featureFlags: FeatureFlags,
+ featureFlags: QSPipelineFlagsRepository,
qsHost: QSTileHost,
customTileAddedRepository: CustomTileAddedSharedPrefsRepository
): CustomTileAddedRepository {
- return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ return if (featureFlags.pipelineHostEnabled) {
customTileAddedRepository
} else {
qsHost
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 966f370..5a5e47a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -27,8 +27,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.qs.QSFactory
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.CustomTile
@@ -39,6 +37,7 @@
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.qs.toProto
@@ -139,7 +138,7 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
private val logger: QSPipelineLogger,
- featureFlags: FeatureFlags,
+ featureFlags: QSPipelineFlagsRepository,
) : CurrentTilesInteractor {
private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
@@ -171,7 +170,7 @@
}
init {
- if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ if (featureFlags.pipelineHostEnabled) {
startTileCollection()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index 224fc1a..0743ba0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,10 +18,9 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import javax.inject.Inject
@SysUISingleton
@@ -30,14 +29,11 @@
constructor(
private val currentTilesInteractor: CurrentTilesInteractor,
private val autoAddInteractor: AutoAddInteractor,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: QSPipelineFlagsRepository,
) : CoreStartable {
override fun start() {
- if (
- featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
- featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
- ) {
+ if (featureFlags.pipelineAutoAddEnabled) {
autoAddInteractor.init(currentTilesInteractor)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
new file mode 100644
index 0000000..551b0f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.qs.pipeline.shared
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** Encapsulate the different QS pipeline flags and their dependencies */
+@SysUISingleton
+class QSPipelineFlagsRepository
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+) {
+
+ /** @see Flags.QS_PIPELINE_NEW_HOST */
+ val pipelineHostEnabled: Boolean
+ get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
+
+ /** @see Flags.QS_PIPELINE_AUTO_ADD */
+ val pipelineAutoAddEnabled: Boolean
+ get() = pipelineHostEnabled && featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index e8683fb..fb99775 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -63,6 +63,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setDialogTitle(R.string.screenrecord_permission_dialog_title)
+ setTitle(R.string.screenrecord_title)
setStartButtonText(R.string.screenrecord_permission_dialog_continue)
setStartButtonOnClickListener { v: View? ->
onStartRecordingClicked?.run()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index df7d88f..014093d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -163,7 +163,6 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -356,7 +355,6 @@
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final KeyguardRootView mKeyguardRootView;
private final QuickSettingsController mQsController;
- private final ShadeInteractor mShadeInteractor;
private final TouchHandler mTouchHandler = new TouchHandler();
private long mDownTime;
@@ -561,6 +559,7 @@
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
+ private float mExpandedFraction = 0;
private float mExpansionDragDownAmountPx = 0;
private boolean mPanelClosedOnDown;
private boolean mHasLayoutedSinceDown;
@@ -710,12 +709,10 @@
VibratorHelper vibratorHelper,
LatencyTracker latencyTracker,
PowerManager powerManager,
- AccessibilityManager accessibilityManager,
- @DisplayId int displayId,
+ AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
- ShadeInteractor shadeInteractor,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -788,7 +785,6 @@
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
- mShadeInteractor = shadeInteractor;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
mGutsManager = gutsManager;
@@ -989,6 +985,7 @@
// Make sure the clock is in the correct position after the unlock animation
// so that it's not in the wrong place when we show the keyguard again.
positionClockAndNotifications(true /* forceClockUpdate */);
+ mScrimController.onUnlockAnimationFinished();
}
private void unlockAnimationStarted(
@@ -1247,6 +1244,13 @@
mKeyguardStatusViewController.init();
}
mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ int oldHeight = oldBottom - oldTop;
+ if (v.getHeight() != oldHeight) {
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
updateClockAppearance();
@@ -2248,7 +2252,7 @@
if (!isFalseTouch(x, y, interactionType)) {
mShadeLog.logFlingExpands(vel, vectorVel, interactionType,
this.mFlingAnimationUtils.getMinVelocityPxPerSecond(),
- getExpandedFraction() > 0.5f, mAllowExpandForSmallExpansion);
+ mExpandedFraction > 0.5f, mAllowExpandForSmallExpansion);
if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
expands = shouldExpandWhenNotFlinging();
} else {
@@ -2423,7 +2427,7 @@
void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScrollLayoutController.updateTopPadding(
mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
- getKeyguardNotificationStaticPadding(), getExpandedFraction()), animate);
+ getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
if (isKeyguardShowing()
&& mKeyguardBypassController.getBypassEnabled()) {
// update the position of the header
@@ -2505,10 +2509,10 @@
private void onHeightUpdated(float expandedHeight) {
if (expandedHeight <= 0) {
mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
- getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
} else if (isFullyExpanded()) {
mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
- getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
}
if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
|| mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
@@ -3434,7 +3438,7 @@
ipw.print("mHintDistance="); ipw.println(mHintDistance);
ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
- ipw.print("getExpandedFraction()="); ipw.println(getExpandedFraction());
+ ipw.print("mExpandedFraction="); ipw.println(mExpandedFraction);
ipw.print("mExpansionDragDownAmountPx="); ipw.println(mExpansionDragDownAmountPx);
ipw.print("mPanelClosedOnDown="); ipw.println(mPanelClosedOnDown);
ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
@@ -3770,7 +3774,7 @@
// don't fling while in keyguard to avoid jump in shade expand animation;
// touch has been intercepted already so flinging here is redundant
- if (mBarState == KEYGUARD && getExpandedFraction() >= 1.0) {
+ if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) {
mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard");
} else {
fling(vel, expand, isFalseTouch(x, y, interactionType));
@@ -3866,16 +3870,6 @@
@VisibleForTesting
void setExpandedHeight(float height) {
debugLog("setExpandedHeight(%.1f)", height);
- int maxPanelTransitionDistance = getMaxPanelTransitionDistance();
- if (maxPanelTransitionDistance == 0) {
- setExpandedFracAndHeight(0, height);
- } else {
- setExpandedFracAndHeight(height / maxPanelTransitionDistance, height);
- }
- }
-
- private void setExpandedFracAndHeight(float frac, float height) {
- mShadeInteractor.setExpansion(frac);
setExpandedHeightInternal(height);
}
@@ -3931,9 +3925,11 @@
mHeightAnimator.end();
}
}
- mQsController.setShadeExpansion(mExpandedHeight, getExpandedFraction());
+ mExpandedFraction = Math.min(1f,
+ maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
mExpansionDragDownAmountPx = h;
- mAmbientState.setExpansionFraction(getExpandedFraction());
+ mAmbientState.setExpansionFraction(mExpandedFraction);
onHeightUpdated(mExpandedHeight);
updateExpansionAndVisibility();
});
@@ -3961,7 +3957,8 @@
/** Sets the expanded height relative to a number from 0 to 1. */
@VisibleForTesting
void setExpandedFraction(float frac) {
- setExpandedFracAndHeight(frac, getMaxPanelTransitionDistance() * frac);
+ final int maxDist = getMaxPanelTransitionDistance();
+ setExpandedHeight(maxDist * frac);
}
float getExpandedHeight() {
@@ -3969,7 +3966,7 @@
}
float getExpandedFraction() {
- return mShadeInteractor.getExpansion().getValue();
+ return mExpandedFraction;
}
@Override
@@ -3991,7 +3988,7 @@
@Override
public boolean isFullyCollapsed() {
- return getExpandedFraction() <= 0.0f;
+ return mExpandedFraction <= 0.0f;
}
@Override
@@ -4150,7 +4147,7 @@
animator.getAnimatedFraction()));
setOverExpansionInternal(expansion, false /* isFromGesture */);
}
- setExpandedHeight((float) animation.getAnimatedValue());
+ setExpandedHeightInternal((float) animation.getAnimatedValue());
});
return animator;
}
@@ -4164,14 +4161,14 @@
@Override
public void updateExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- getExpandedFraction(), isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
@Override
public boolean isExpanded() {
- return getExpandedFraction() > 0f
+ return mExpandedFraction > 0f
|| mInstantExpanding
|| isPanelVisibleBecauseOfHeadsUp()
|| mTracking
@@ -4929,7 +4926,7 @@
mMotionAborted = false;
mPanelClosedOnDown = isFullyCollapsed();
mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown,
- getExpandedFraction());
+ mExpandedFraction);
mCollapsedAndHeadsUpOnDown = false;
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
@@ -5148,7 +5145,7 @@
mMinExpandHeight = 0.0f;
mPanelClosedOnDown = isFullyCollapsed();
mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown,
- getExpandedFraction());
+ mExpandedFraction);
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mMotionAborted = false;
@@ -5207,7 +5204,7 @@
if (isFullyCollapsed()) {
// If panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;
- mShadeLog.logHasVibrated(mHasVibratedOnOpen, getExpandedFraction());
+ mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
}
addMovement(event);
if (!isFullyCollapsed()) {
@@ -5243,7 +5240,7 @@
// otherwise {@link NotificationStackScrollLayout}
// wrongly enables stack height updates at the start of lockscreen swipe-up
mAmbientState.setSwipingUp(h <= 0);
- setExpandedHeight(newHeight);
+ setExpandedHeightInternal(newHeight);
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 832a25b..802ed80 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -21,8 +21,6 @@
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
-import android.media.AudioManager;
-import android.media.session.MediaSessionLegacyHelper;
import android.os.PowerManager;
import android.util.Log;
import android.view.GestureDetector;
@@ -47,6 +45,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -101,6 +100,7 @@
private final NotificationInsetsController mNotificationInsetsController;
private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlags mFeatureFlags;
+ private final KeyEventInteractor mKeyEventInteractor;
private GestureDetector mPulsingWakeupGestureHandler;
private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -164,7 +164,8 @@
FeatureFlags featureFlags,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
- BouncerLogger bouncerLogger) {
+ BouncerLogger bouncerLogger,
+ KeyEventInteractor keyEventInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -190,6 +191,7 @@
mNotificationInsetsController = notificationInsetsController;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlags = featureFlags;
+ mKeyEventInteractor = keyEventInteractor;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -457,44 +459,17 @@
@Override
public boolean interceptMediaKey(KeyEvent event) {
- return mService.interceptMediaKey(event);
+ return mKeyEventInteractor.interceptMediaKey(event);
}
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mService.dispatchKeyEventPreIme(event);
+ return mKeyEventInteractor.dispatchKeyEventPreIme(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_BACK:
- if (!down) {
- mBackActionInteractor.onBackRequested();
- }
- return true;
- case KeyEvent.KEYCODE_MENU:
- if (!down) {
- return mService.onMenuPressed();
- }
- break;
- case KeyEvent.KEYCODE_SPACE:
- if (!down) {
- return mService.onSpacePressed();
- }
- break;
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- case KeyEvent.KEYCODE_VOLUME_UP:
- if (mStatusBarStateController.isDozing()) {
- MediaSessionLegacyHelper.getHelper(mView.getContext())
- .sendVolumeKeyEvent(
- event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
- return true;
- }
- break;
- }
- return false;
+ return mKeyEventInteractor.dispatchKeyEvent(event);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 76dca4c..ebb9935 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -34,34 +34,13 @@
/** ShadeModel information regarding shade expansion events */
val shadeModel: Flow<ShadeModel>
- /**
- * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
- * expanded and `0` is fully collapsed. Quick Settings can be expanded without a fully expanded
- * shade.
- */
+ /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */
val qsExpansion: StateFlow<Float>
- /**
- * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
- * expanded and `0` is fully collapsed.
- */
- val expansion: StateFlow<Float>
-
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
- /**
- * Set shade expansion to a value from `0` to `1` representing the amount the shade has expanded
- * where `1` is fully expanded and `0` is fully collapsed.
- */
- fun setExpansion(expansion: Float)
-
- /**
- * Set quick settings expansion to a value from `0` to `1` representing the amount quick
- * settings has expanded where `1` is fully expanded and `0` is fully collapsed.
- */
fun setQsExpansion(qsExpansion: Float)
-
fun setUdfpsTransitionToFullShadeProgress(progress: Float)
}
@@ -99,17 +78,9 @@
private val _qsExpansion = MutableStateFlow(0f)
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
- private val _expansion = MutableStateFlow(0f)
- override val expansion: StateFlow<Float> = _expansion.asStateFlow()
-
private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
_udfpsTransitionToFullShadeProgress.asStateFlow()
-
- override fun setExpansion(expansion: Float) {
- _expansion.value = expansion
- }
-
override fun setQsExpansion(qsExpansion: Float) {
_qsExpansion.value = qsExpansion
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index efe3eb4..6fde84a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -39,29 +38,18 @@
@Inject
constructor(
@Application scope: CoroutineScope,
- private val shadeRepository: ShadeRepository,
disableFlagsRepository: DisableFlagsRepository,
keyguardRepository: KeyguardRepository,
userSetupRepository: UserSetupRepository,
deviceProvisionedController: DeviceProvisionedController,
userInteractor: UserInteractor,
) {
- /**
- * Value from `0` to `1` representing the amount the shade has expanded where `1` is fully
- * expanded and `0` is fully collapsed.
- */
- val expansion = shadeRepository.expansion
-
/** Emits true if the shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean> =
disableFlagsRepository.disableFlags
.map { it.isShadeEnabled() }
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
- fun setExpansion(expansion: Float) {
- shadeRepository.setExpansion(expansion)
- }
-
/** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
val isExpandToQsEnabled: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index d3c19b7..5042f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -31,56 +31,68 @@
) {
fun logInitialClick(
entry: NotificationEntry?,
+ index: Integer?,
pendingIntent: PendingIntent
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
str2 = entry?.ranking?.channel?.id
- str3 = pendingIntent.intent.toString()
+ str3 = pendingIntent.toString()
+ int1 = index?.toInt() ?: Int.MIN_VALUE
}, {
- "ACTION CLICK $str1 (channel=$str2) for pending intent $str3"
+ "ACTION CLICK $str1 (channel=$str2) for pending intent $str3 at index $int1"
})
}
fun logRemoteInputWasHandled(
- entry: NotificationEntry?
+ entry: NotificationEntry?,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Triggered remote input (for $str1))"
+ " [Action click] Triggered remote input (for $str1) at index $int1"
})
}
fun logStartingIntentWithDefaultHandler(
entry: NotificationEntry?,
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry?.key
- str2 = pendingIntent.intent.toString()
+ str2 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Launching intent $str2 via default handler (for $str1)"
+ " [Action click] Launching intent $str2 via default handler (for $str1 at index $int1)"
})
}
fun logWaitingToCloseKeyguard(
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = pendingIntent.intent.toString()
+ str1 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Intent $str1 launches an activity, dismissing keyguard first..."
+ " [Action click] Intent $str1 at index $int1 launches an activity, dismissing " +
+ "keyguard first..."
})
}
fun logKeyguardGone(
- pendingIntent: PendingIntent
+ pendingIntent: PendingIntent,
+ index: Int?
) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = pendingIntent.intent.toString()
+ str1 = pendingIntent.toString()
+ int1 = index ?: Int.MIN_VALUE
}, {
- " [Action click] Keyguard dismissed, calling default handler for intent $str1"
+ " [Action click] Keyguard dismissed, calling default handler for intent $str1 at " +
+ "index $int1"
})
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
index abf81c5..692a997 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt
@@ -2,10 +2,12 @@
import android.app.Notification
import android.os.RemoteException
+import android.util.Log
import com.android.internal.statusbar.IStatusBarService
import com.android.internal.statusbar.NotificationVisibility
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.util.Assert
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -21,7 +23,8 @@
@SysUISingleton
public class NotificationClickNotifier @Inject constructor(
val barService: IStatusBarService,
- @Main val mainExecutor: Executor
+ @Main val mainExecutor: Executor,
+ @UiBackground val backgroundExecutor: Executor
) {
val listeners = mutableListOf<NotificationInteractionListener>()
@@ -48,13 +51,14 @@
visibility: NotificationVisibility,
generatedByAssistant: Boolean
) {
- try {
- barService.onNotificationActionClick(
- key, actionIndex, action, visibility, generatedByAssistant)
- } catch (e: RemoteException) {
- // nothing
+ backgroundExecutor.execute {
+ try {
+ barService.onNotificationActionClick(
+ key, actionIndex, action, visibility, generatedByAssistant)
+ } catch (e: RemoteException) {
+ // nothing
+ }
}
-
mainExecutor.execute {
notifyListenersAboutInteraction(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index da84afe..8089fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -119,11 +119,14 @@
mPowerInteractor.wakeUpIfDozing(
"NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
+ Integer actionIndex = (Integer)
+ view.getTag(com.android.internal.R.id.notification_action_index_tag);
+
final NotificationEntry entry = getNotificationForParent(view.getParent());
- mLogger.logInitialClick(entry, pendingIntent);
+ mLogger.logInitialClick(entry, actionIndex, pendingIntent);
if (handleRemoteInput(view, pendingIntent)) {
- mLogger.logRemoteInputWasHandled(entry);
+ mLogger.logRemoteInputWasHandled(entry, actionIndex);
return true;
}
@@ -141,9 +144,9 @@
}
Notification.Action action = getActionFromView(view, entry, pendingIntent);
return mCallback.handleRemoteViewClick(view, pendingIntent,
- action == null ? false : action.isAuthenticationRequired(), () -> {
+ action == null ? false : action.isAuthenticationRequired(), actionIndex, () -> {
Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view);
- mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent);
+ mLogger.logStartingIntentWithDefaultHandler(entry, pendingIntent, actionIndex);
boolean started = RemoteViews.startPendingIntent(view, pendingIntent, options);
if (started) releaseNotificationIfKeptForRemoteInputHistory(entry);
return started;
@@ -692,11 +695,13 @@
* @param view
* @param pendingIntent
* @param appRequestedAuth
+ * @param actionIndex
* @param defaultHandler
* @return true iff the click was handled
*/
boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
- boolean appRequestedAuth, ClickHandler defaultHandler);
+ boolean appRequestedAuth, @Nullable Integer actionIndex,
+ ClickHandler defaultHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index d6a14604..6dd24ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -48,8 +48,10 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -420,6 +422,25 @@
}
}
+ /** Returns the id of the currently rendering clock */
+ public String getClockId() {
+ if (mView == null) {
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+
+ View clockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+ if (clockSwitch == null) {
+ Log.e(TAG, "Clock container was missing");
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+ if (!(clockSwitch instanceof KeyguardClockSwitch)) {
+ Log.e(TAG, "Clock container was incorrect type: " + clockSwitch);
+ return KeyguardClockSwitch.MISSING_CLOCK_ID;
+ }
+
+ return ((KeyguardClockSwitch) clockSwitch).getClockId();
+ }
+
private void beginInteractionJankMonitor() {
final boolean shouldPost =
(mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1);
@@ -429,6 +450,7 @@
Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null);
} else {
Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView)
+ .setTag(getClockId())
.setDeferMonitorForAnimationStart(false);
mInteractionJankMonitor.begin(builder);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index ed80f33..5558ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -2,13 +2,17 @@
# Bug component: 78010
-aaliomer@google.com
+aioana@google.com
aroederer@google.com
+iyz@google.com
jeffdq@google.com
juliacr@google.com
juliatuttle@google.com
+kurucz@google.com
+liuyining@google.com
lynhan@google.com
-steell@google.com
+matiashe@google.com
+valiiftime@google.com
yurilin@google.com
per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
new file mode 100644
index 0000000..26dfe3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -0,0 +1,684 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.Bundle
+import android.os.Trace
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.ColorInt
+import androidx.annotation.VisibleForTesting
+import androidx.collection.ArrayMap
+import com.android.app.animation.Interpolators
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.internal.util.ContrastColorUtil
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.ViewRefactorFlag
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import java.util.function.Function
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * Controller class for [NotificationIconContainer]. This implementation serves as a temporary
+ * wrapper around [NotificationIconContainerViewBinder], so that external code can continue to
+ * depend on the [NotificationIconAreaController] interface. Once
+ * [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder
+ * can be used directly.
+ */
+@SysUISingleton
+class NotificationIconAreaControllerViewBinderWrapperImpl
+@Inject
+constructor(
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val wakeUpCoordinator: NotificationWakeUpCoordinator,
+ private val bypassController: KeyguardBypassController,
+ private val mediaManager: NotificationMediaManager,
+ notificationListener: NotificationListener,
+ private val dozeParameters: DozeParameters,
+ private val sectionStyleProvider: SectionStyleProvider,
+ private val bubblesOptional: Optional<Bubbles>,
+ demoModeController: DemoModeController,
+ darkIconDispatcher: DarkIconDispatcher,
+ featureFlags: FeatureFlags,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
+ private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
+ private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+) :
+ NotificationIconAreaController,
+ DarkIconDispatcher.DarkReceiver,
+ StatusBarStateController.StateListener,
+ NotificationWakeUpCoordinator.WakeUpListener,
+ DemoMode {
+
+ private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
+ private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
+ private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
+ private val tintAreas = ArrayList<Rect>()
+
+ private var iconSize = 0
+ private var iconHPadding = 0
+ private var iconTint = Color.WHITE
+ private var notificationEntries = listOf<ListEntry>()
+ private var notificationIconArea: View? = null
+ private var notificationIcons: NotificationIconContainer? = null
+ private var shelfIcons: NotificationIconContainer? = null
+ private var aodIcons: NotificationIconContainer? = null
+ private var aodBindJob: DisposableHandle? = null
+ private var aodIconAppearTranslation = 0
+ private var animationsEnabled = false
+ private var aodIconTint = 0
+ private var aodIconsVisible = false
+ private var showLowPriority = true
+
+ @VisibleForTesting
+ val settingsListener: NotificationListener.NotificationSettingsListener =
+ object : NotificationListener.NotificationSettingsListener {
+ override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
+ showLowPriority = !hideSilentStatusIcons
+ updateStatusBarIcons()
+ }
+ }
+
+ init {
+ statusBarStateController.addCallback(this)
+ wakeUpCoordinator.addListener(this)
+ demoModeController.addCallback(this)
+ notificationListener.addNotificationSettingsListener(settingsListener)
+ initializeNotificationAreaViews(context)
+ reloadAodColor()
+ darkIconDispatcher.addDarkReceiver(this)
+ }
+
+ @VisibleForTesting
+ fun shouldShowLowPriorityIcons(): Boolean {
+ return showLowPriority
+ }
+
+ /** Called by the Keyguard*ViewController whose view contains the aod icons. */
+ override fun setupAodIcons(aodIcons: NotificationIconContainer) {
+ val changed = this.aodIcons != null && aodIcons !== this.aodIcons
+ if (changed) {
+ this.aodIcons!!.setAnimationsEnabled(false)
+ this.aodIcons!!.removeAllViews()
+ aodBindJob?.dispose()
+ }
+ this.aodIcons = aodIcons
+ this.aodIcons!!.setOnLockScreen(true)
+ aodBindJob = NotificationIconContainerViewBinder.bind(aodIcons, aodIconsViewModel)
+ updateAodIconsVisibility(animate = false, forceUpdate = changed)
+ updateAnimations()
+ if (changed) {
+ updateAodNotificationIcons()
+ }
+ updateIconLayoutParams(context)
+ }
+
+ override fun setupShelf(notificationShelfController: NotificationShelfController) =
+ NotificationShelfViewBinderWrapperControllerImpl.unsupported
+
+ override fun setShelfIcons(icons: NotificationIconContainer) {
+ if (shelfRefactor.expectEnabled()) {
+ NotificationIconContainerViewBinder.bind(icons, shelfIconsViewModel)
+ shelfIcons = icons
+ }
+ }
+
+ override fun onDensityOrFontScaleChanged(context: Context) {
+ updateIconLayoutParams(context)
+ }
+
+ /** Returns the view that represents the notification area. */
+ override fun getNotificationInnerAreaView(): View? {
+ return notificationIconArea
+ }
+
+ /**
+ * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
+ * color that should be used to tint any icons in the notification area.
+ *
+ * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
+ * @param darkIntensity
+ */
+ override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
+ this.tintAreas.clear()
+ this.tintAreas.addAll(tintAreas)
+ if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
+ this.iconTint = iconTint
+ }
+ applyNotificationIconsTint()
+ }
+
+ /** Updates the notifications with the given list of notifications to display. */
+ override fun updateNotificationIcons(entries: List<ListEntry>) {
+ notificationEntries = entries
+ updateNotificationIcons()
+ }
+
+ private fun updateStatusBarIcons() {
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.statusBarIcon },
+ notificationIcons,
+ showAmbient = false /* showAmbient */,
+ showLowPriority = showLowPriority,
+ hideDismissed = true /* hideDismissed */,
+ hideRepliedMessages = true /* hideRepliedMessages */,
+ hideCurrentMedia = false /* hideCurrentMedia */,
+ hidePulsing = false /* hidePulsing */
+ )
+ }
+
+ override fun updateAodNotificationIcons() {
+ if (aodIcons == null) {
+ return
+ }
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.aodIcon },
+ aodIcons,
+ showAmbient = false /* showAmbient */,
+ showLowPriority = true /* showLowPriority */,
+ hideDismissed = true /* hideDismissed */,
+ hideRepliedMessages = true /* hideRepliedMessages */,
+ hideCurrentMedia = true /* hideCurrentMedia */,
+ hidePulsing = bypassController.bypassEnabled /* hidePulsing */
+ )
+ }
+
+ override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
+ notificationIcons!!.showIconIsolated(icon, animated)
+ }
+
+ override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
+ notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
+ }
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (aodIcons == null) {
+ return
+ }
+ val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking)
+ aodIcons!!.setDozing(isDozing, animate, 0)
+ }
+
+ override fun setAnimationsEnabled(enabled: Boolean) {
+ animationsEnabled = enabled
+ updateAnimations()
+ }
+
+ override fun onStateChanged(newState: Int) {
+ updateAodIconsVisibility(animate = false, forceUpdate = false)
+ updateAnimations()
+ }
+
+ override fun onThemeChanged() {
+ reloadAodColor()
+ updateAodIconColors()
+ }
+
+ override fun getHeight(): Int {
+ return if (aodIcons == null) 0 else aodIcons!!.height
+ }
+
+ @VisibleForTesting
+ fun appearAodIcons() {
+ if (aodIcons == null) {
+ return
+ }
+ if (screenOffAnimationController.shouldAnimateAodIcons()) {
+ aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+ aodIcons!!.alpha = 0f
+ animateInAodIconTranslation()
+ aodIcons!!
+ .animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ } else {
+ aodIcons!!.alpha = 1.0f
+ aodIcons!!.translationY = 0f
+ }
+ }
+
+ override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+ var animate = true
+ if (!bypassController.bypassEnabled) {
+ animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ // We only want the appear animations to happen when the notifications get fully hidden,
+ // since otherwise the unhide animation overlaps
+ animate = animate and isFullyHidden
+ }
+ updateAodIconsVisibility(animate, false /* force */)
+ updateAodNotificationIcons()
+ updateAodIconColors()
+ }
+
+ override fun onPulseExpansionChanged(expandingChanged: Boolean) {
+ if (expandingChanged) {
+ updateAodIconsVisibility(animate = true, forceUpdate = false)
+ }
+ }
+
+ override fun demoCommands(): List<String> {
+ val commands = ArrayList<String>()
+ commands.add(DemoMode.COMMAND_NOTIFICATIONS)
+ return commands
+ }
+
+ override fun dispatchDemoCommand(command: String, args: Bundle) {
+ if (notificationIconArea != null) {
+ val visible = args.getString("visible")
+ val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
+ notificationIconArea?.visibility = vis
+ }
+ }
+
+ override fun onDemoModeFinished() {
+ if (notificationIconArea != null) {
+ notificationIconArea?.visibility = View.VISIBLE
+ }
+ }
+
+ private fun inflateIconArea(inflater: LayoutInflater): View {
+ return inflater.inflate(R.layout.notification_icon_area, null)
+ }
+
+ /** Initializes the views that will represent the notification area. */
+ private fun initializeNotificationAreaViews(context: Context) {
+ reloadDimens(context)
+ val layoutInflater = LayoutInflater.from(context)
+ notificationIconArea = inflateIconArea(layoutInflater)
+ notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
+ NotificationIconContainerViewBinder.bind(notificationIcons!!, statusBarIconsViewModel)
+ }
+
+ private fun updateIconLayoutParams(context: Context) {
+ reloadDimens(context)
+ val params = generateIconLayoutParams()
+ for (i in 0 until notificationIcons!!.childCount) {
+ val child = notificationIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ if (shelfIcons != null) {
+ for (i in 0 until shelfIcons!!.childCount) {
+ val child = shelfIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ }
+ if (aodIcons != null) {
+ for (i in 0 until aodIcons!!.childCount) {
+ val child = aodIcons!!.getChildAt(i)
+ child.layoutParams = params
+ }
+ }
+ }
+
+ private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
+ return FrameLayout.LayoutParams(
+ iconSize + 2 * iconHPadding,
+ statusBarWindowController.statusBarHeight
+ )
+ }
+
+ private fun reloadDimens(context: Context) {
+ val res = context.resources
+ iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
+ iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+ aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ }
+
+ private fun shouldShowNotificationIcon(
+ entry: NotificationEntry,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ hideDismissed: Boolean,
+ hideRepliedMessages: Boolean,
+ hideCurrentMedia: Boolean,
+ hidePulsing: Boolean
+ ): Boolean {
+ if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
+ return false
+ }
+ if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
+ return false
+ }
+ if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
+ return false
+ }
+ if (entry.isRowDismissed && hideDismissed) {
+ return false
+ }
+ if (hideRepliedMessages && entry.isLastMessageFromReply) {
+ return false
+ }
+ // showAmbient == show in shade but not shelf
+ if (!showAmbient && entry.shouldSuppressStatusBar()) {
+ return false
+ }
+ if (
+ hidePulsing &&
+ entry.showingPulsing() &&
+ (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
+ ) {
+ return false
+ }
+ return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
+ false
+ } else true
+ }
+
+ private fun updateNotificationIcons() {
+ Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
+ updateStatusBarIcons()
+ updateShelfIcons()
+ updateAodNotificationIcons()
+ applyNotificationIconsTint()
+ Trace.endSection()
+ }
+
+ private fun updateShelfIcons() {
+ if (shelfIcons == null) {
+ return
+ }
+ updateIconsForLayout(
+ { entry: NotificationEntry -> entry.icons.shelfIcon },
+ shelfIcons,
+ showAmbient = true,
+ showLowPriority = true,
+ hideDismissed = false,
+ hideRepliedMessages = false,
+ hideCurrentMedia = false,
+ hidePulsing = false
+ )
+ }
+
+ /**
+ * Updates the notification icons for a host layout. This will ensure that the notification host
+ * layout will have the same icons like the ones in here.
+ *
+ * @param function A function to look up an icon view based on an entry
+ * @param hostLayout which layout should be updated
+ * @param showAmbient should ambient notification icons be shown
+ * @param showLowPriority should icons from silent notifications be shown
+ * @param hideDismissed should dismissed icons be hidden
+ * @param hideRepliedMessages should messages that have been replied to be hidden
+ * @param hidePulsing should pulsing notifications be hidden
+ */
+ private fun updateIconsForLayout(
+ function: Function<NotificationEntry, StatusBarIconView?>,
+ hostLayout: NotificationIconContainer?,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ hideDismissed: Boolean,
+ hideRepliedMessages: Boolean,
+ hideCurrentMedia: Boolean,
+ hidePulsing: Boolean,
+ ) {
+ val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
+ // Filter out ambient notifications and notification children.
+ for (i in notificationEntries.indices) {
+ val entry = notificationEntries[i].representativeEntry
+ if (entry != null && entry.row != null) {
+ if (
+ shouldShowNotificationIcon(
+ entry,
+ showAmbient,
+ showLowPriority,
+ hideDismissed,
+ hideRepliedMessages,
+ hideCurrentMedia,
+ hidePulsing
+ )
+ ) {
+ val iconView = function.apply(entry)
+ if (iconView != null) {
+ toShow.add(iconView)
+ }
+ }
+ }
+ }
+
+ // In case we are changing the suppression of a group, the replacement shouldn't flicker
+ // and it should just be replaced instead. We therefore look for notifications that were
+ // just replaced by the child or vice-versa to suppress this.
+ val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
+ val toRemove = ArrayList<View>()
+ for (i in 0 until hostLayout!!.childCount) {
+ val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
+ if (!toShow.contains(child)) {
+ var iconWasReplaced = false
+ val removedGroupKey = child.notification.groupKey
+ for (j in toShow.indices) {
+ val candidate = toShow[j]
+ if (
+ candidate.sourceIcon.sameAs(child.sourceIcon) &&
+ candidate.notification.groupKey == removedGroupKey
+ ) {
+ if (!iconWasReplaced) {
+ iconWasReplaced = true
+ } else {
+ iconWasReplaced = false
+ break
+ }
+ }
+ }
+ if (iconWasReplaced) {
+ var statusBarIcons = replacingIcons[removedGroupKey]
+ if (statusBarIcons == null) {
+ statusBarIcons = ArrayList()
+ replacingIcons[removedGroupKey] = statusBarIcons
+ }
+ statusBarIcons.add(child.statusBarIcon)
+ }
+ toRemove.add(child)
+ }
+ }
+ // removing all duplicates
+ val duplicates = ArrayList<String?>()
+ for (key in replacingIcons.keys) {
+ val statusBarIcons = replacingIcons[key]!!
+ if (statusBarIcons.size != 1) {
+ duplicates.add(key)
+ }
+ }
+ replacingIcons.removeAll(duplicates)
+ hostLayout.setReplacingIcons(replacingIcons)
+ val toRemoveCount = toRemove.size
+ for (i in 0 until toRemoveCount) {
+ hostLayout.removeView(toRemove[i])
+ }
+ val params = generateIconLayoutParams()
+ for (i in toShow.indices) {
+ val v = toShow[i]
+ // The view might still be transiently added if it was just removed and added again
+ hostLayout.removeTransientView(v)
+ if (v.parent == null) {
+ if (hideDismissed) {
+ v.setOnDismissListener(updateStatusBarIcons)
+ }
+ hostLayout.addView(v, i, params)
+ }
+ }
+ hostLayout.setChangingViewPositions(true)
+ // Re-sort notification icons
+ val childCount = hostLayout.childCount
+ for (i in 0 until childCount) {
+ val actual = hostLayout.getChildAt(i)
+ val expected = toShow[i]
+ if (actual === expected) {
+ continue
+ }
+ hostLayout.removeView(expected)
+ hostLayout.addView(expected, i)
+ }
+ hostLayout.setChangingViewPositions(false)
+ hostLayout.setReplacingIcons(null)
+ }
+
+ /** Applies [.mIconTint] to the notification icons. */
+ private fun applyNotificationIconsTint() {
+ for (i in 0 until notificationIcons!!.childCount) {
+ val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
+ if (iv.width != 0) {
+ updateTintForIcon(iv, iconTint)
+ } else {
+ iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
+ }
+ }
+ updateAodIconColors()
+ }
+
+ private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
+ val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
+ var color = StatusBarIconView.NO_COLOR
+ val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
+ if (colorize) {
+ color = DarkIconDispatcher.getTint(tintAreas, v, tint)
+ }
+ v.staticDrawableColor = color
+ v.setDecorColor(tint)
+ }
+
+ private fun updateAnimations() {
+ val inShade = statusBarStateController.state == StatusBarState.SHADE
+ if (aodIcons != null) {
+ aodIcons!!.setAnimationsEnabled(animationsEnabled && !inShade)
+ }
+ notificationIcons!!.setAnimationsEnabled(animationsEnabled && inShade)
+ }
+
+ private fun animateInAodIconTranslation() {
+ aodIcons!!
+ .animate()
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
+ .translationY(0f)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ }
+
+ private fun reloadAodColor() {
+ aodIconTint =
+ Utils.getColorAttrDefaultColor(
+ context,
+ R.attr.wallpaperTextColor,
+ DEFAULT_AOD_ICON_COLOR
+ )
+ }
+
+ private fun updateAodIconColors() {
+ if (aodIcons != null) {
+ for (i in 0 until aodIcons!!.childCount) {
+ val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
+ if (iv.width != 0) {
+ updateTintForIcon(iv, aodIconTint)
+ } else {
+ iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
+ }
+ }
+ }
+ }
+
+ private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) {
+ if (aodIcons == null) {
+ return
+ }
+ var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden)
+
+ // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
+ // playing, in which case we want them to be visible since we're animating in the AOD UI and
+ // will be switching to KEYGUARD shortly.
+ if (
+ statusBarStateController.state != StatusBarState.KEYGUARD &&
+ !screenOffAnimationController.shouldShowAodIconsWhenShade()
+ ) {
+ visible = false
+ }
+ if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) {
+ visible = false
+ }
+ if (aodIconsVisible != visible || forceUpdate) {
+ aodIconsVisible = visible
+ aodIcons!!.animate().cancel()
+ if (animate) {
+ val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE
+ if (aodIconsVisible) {
+ if (wasFullyInvisible) {
+ // No fading here, let's just appear the icons instead!
+ aodIcons!!.visibility = View.VISIBLE
+ aodIcons!!.alpha = 1.0f
+ appearAodIcons()
+ } else {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInAodIconTranslation()
+ // We were fading out, let's fade in instead
+ CrossFadeHelper.fadeIn(aodIcons)
+ }
+ } else {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInAodIconTranslation()
+ CrossFadeHelper.fadeOut(aodIcons)
+ }
+ } else {
+ aodIcons!!.alpha = 1.0f
+ aodIcons!!.translationY = 0f
+ aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
+ }
+ }
+ }
+
+ companion object {
+ private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+
+ @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
new file mode 100644
index 0000000..8293bb3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import kotlinx.coroutines.DisposableHandle
+
+/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
+object NotificationIconContainerViewBinder {
+ fun bind(
+ view: NotificationIconContainer,
+ viewModel: NotificationIconContainerViewModel,
+ ): DisposableHandle {
+ return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) {} }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
new file mode 100644
index 0000000..f68b0ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the row of notification icons displayed on the always-on display. */
+class NotificationIconContainerAlwaysOnDisplayViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
new file mode 100644
index 0000000..933c76f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the overflow row of notification icons displayed in the notification shade. */
+class NotificationIconContainerShelfViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
new file mode 100644
index 0000000..2217646
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+import javax.inject.Inject
+
+/** View-model for the row of notification icons displayed in the status bar, */
+class NotificationIconContainerStatusBarViewModel @Inject constructor() :
+ NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
new file mode 100644
index 0000000..892b2be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+
+/**
+ * View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and
+ * AOD.
+ */
+interface NotificationIconContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
index b002330..c276827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
@@ -20,7 +20,9 @@
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
+import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.ImageFloatingTextView
+import com.android.internal.widget.MessagingLayout
import javax.inject.Inject
class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
@@ -35,6 +37,10 @@
TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
ImageFloatingTextView::class.java.name ->
PrecomputedImageFloatingTextView(context, attrs)
+ MessagingLayout::class.java.name ->
+ MessagingLayout(context, attrs).apply { setPrecomputedTextEnabled(true) }
+ ConversationLayout::class.java.name ->
+ ConversationLayout(context, attrs).apply { setPrecomputedTextEnabled(true) }
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 22a87a7..b92c51f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -64,8 +64,10 @@
override fun setOnClickListener(listener: View.OnClickListener) = unsupported
- private val unsupported: Nothing
- get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
+ companion object {
+ val unsupported: Nothing
+ get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
+ }
}
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
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 4668aa4..6db8df9 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
@@ -28,7 +28,6 @@
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
-import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
@@ -150,6 +149,7 @@
public class NotificationStackScrollLayoutController {
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final String HIGH_PRIORITY = "high_priority";
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 5c28be3..af09bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,7 +25,6 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
import android.view.View;
@@ -276,19 +275,11 @@
void userActivity();
- boolean interceptMediaKey(KeyEvent event);
-
- boolean dispatchKeyEventPreIme(KeyEvent event);
-
- boolean onMenuPressed();
-
void endAffordanceLaunch();
/** Should the keyguard be hidden immediately in response to a back press/gesture. */
boolean shouldKeyguardHideImmediately();
- boolean onSpacePressed();
-
void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction,
Runnable cancelAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 8ffd43a..ccb87bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -89,7 +89,6 @@
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -2636,44 +2635,6 @@
}
@Override
- public boolean interceptMediaKey(KeyEvent event) {
- return mState == StatusBarState.KEYGUARD
- && mStatusBarKeyguardViewManager.interceptMediaKey(event);
- }
-
- /**
- * While IME is active and a BACK event is detected, check with
- * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event
- * should be handled before routing to IME, in order to prevent the user having to hit back
- * twice to exit bouncer.
- */
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_BACK:
- if (mState == StatusBarState.KEYGUARD
- && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) {
- return mBackActionInteractor.onBackRequested();
- }
- }
- return false;
- }
-
- protected boolean shouldUnlockOnMenuPressed() {
- return mDeviceInteractive && mState != StatusBarState.SHADE
- && mStatusBarKeyguardViewManager.shouldDismissOnMenuPressed();
- }
-
- @Override
- public boolean onMenuPressed() {
- if (shouldUnlockOnMenuPressed()) {
- mShadeController.animateCollapseShadeForced();
- return true;
- }
- return false;
- }
-
- @Override
public void endAffordanceLaunch() {
releaseGestureWakeLock();
mCameraLauncherLazy.get().setLaunchingAffordance(false);
@@ -2692,15 +2653,6 @@
return (isScrimmedBouncer || isBouncerOverDream);
}
- @Override
- public boolean onSpacePressed() {
- if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- mShadeController.animateCollapseShadeForced();
- return true;
- }
- return false;
- }
-
private void showBouncerOrLockScreenIfKeyguard() {
// If the keyguard is animating away, we aren't really the keyguard anymore and should not
// show the bouncer/lockscreen.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 0bf0f4b..d22ed38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.systemui.statusbar.phone;
import android.content.Context;
@@ -43,6 +58,8 @@
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
+import org.jetbrains.annotations.NotNull;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -55,13 +72,13 @@
* normally reserved for notifications.
*/
@SysUISingleton
-public class NotificationIconAreaController implements
+public class LegacyNotificationIconAreaControllerImpl implements
+ NotificationIconAreaController,
DarkReceiver,
StatusBarStateController.StateListener,
NotificationWakeUpCoordinator.WakeUpListener,
DemoMode {
- public static final String HIGH_PRIORITY = "high_priority";
private static final long AOD_ICONS_APPEAR_DURATION = 200;
@ColorInt
private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
@@ -110,7 +127,7 @@
};
@Inject
- public NotificationIconAreaController(
+ public LegacyNotificationIconAreaControllerImpl(
Context context,
StatusBarStateController statusBarStateController,
NotificationWakeUpCoordinator wakeUpCoordinator,
@@ -192,7 +209,7 @@
}
}
- public void onDensityOrFontScaleChanged(Context context) {
+ public void onDensityOrFontScaleChanged(@NotNull Context context) {
updateIconLayoutParams(context);
}
@@ -493,7 +510,7 @@
mNotificationIcons.showIconIsolated(icon, animated);
}
- public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
+ public void setIsolatedIconLocation(@NotNull Rect iconDrawingRect, boolean requireStateUpdate) {
mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
new file mode 100644
index 0000000..0079f7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.ListEntry
+
+/**
+ * A controller for the space in the status bar to the left of the system icons. This area is
+ * normally reserved for notifications.
+ */
+interface NotificationIconAreaController {
+ /** Called by the Keyguard*ViewController whose view contains the aod icons. */
+ fun setupAodIcons(aodIcons: NotificationIconContainer)
+ fun setupShelf(notificationShelfController: NotificationShelfController)
+ fun setShelfIcons(icons: NotificationIconContainer)
+ fun onDensityOrFontScaleChanged(context: Context)
+
+ /** Returns the view that represents the notification area. */
+ fun getNotificationInnerAreaView(): View?
+
+ /** Updates the notifications with the given list of notifications to display. */
+ fun updateNotificationIcons(entries: List<@JvmSuppressWildcards ListEntry>)
+ fun updateAodNotificationIcons()
+ fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean)
+ fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean)
+ fun setAnimationsEnabled(enabled: Boolean)
+ fun onThemeChanged()
+ fun getHeight(): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
new file mode 100644
index 0000000..d1ddd51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
+import dagger.Module
+import dagger.Provides
+import javax.inject.Provider
+
+@Module
+object NotificationIconAreaControllerModule {
+ @Provides
+ fun provideNotificationIconAreaControllerImpl(
+ featureFlags: FeatureFlags,
+ legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
+ newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
+ ): NotificationIconAreaController =
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ newProvider.get()
+ } else {
+ legacyProvider.get()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index fc66138..62a8cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -709,6 +709,11 @@
}
}
+ public void onUnlockAnimationFinished() {
+ mAnimatingPanelExpansionOnUnlock = false;
+ applyAndDispatchState();
+ }
+
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 7fe01825..a6284e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -32,6 +32,8 @@
import android.view.View;
import android.view.ViewParent;
+import androidx.annotation.Nullable;
+
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -254,16 +256,16 @@
@Override
public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
- boolean appRequestedAuth,
+ boolean appRequestedAuth, @Nullable Integer actionIndex,
NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity || appRequestedAuth) {
- mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent);
+ mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent, actionIndex);
final boolean afterKeyguardGone = mActivityIntentHelper
.wouldPendingLaunchResolverActivity(pendingIntent,
mLockscreenUserManager.getCurrentUserId());
mActivityStarter.dismissKeyguardThenExecute(() -> {
- mActionClickLogger.logKeyguardGone(pendingIntent);
+ mActionClickLogger.logKeyguardGone(pendingIntent, actionIndex);
try {
ActivityManager.getService().resumeAppSwitches();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1bceb29..cd1afc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -223,10 +223,14 @@
}
.setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
true /* animate */)
- interactionJankMonitor.begin(
- notifShadeWindowControllerLazy.get().windowRootView,
- CUJ_SCREEN_OFF_SHOW_AOD
- )
+ val builder = InteractionJankMonitor.Configuration.Builder
+ .withView(
+ InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD,
+ notifShadeWindowControllerLazy.get().windowRootView
+ )
+ .setTag(statusBarStateControllerImpl.getClockId())
+
+ interactionJankMonitor.begin(builder)
}
override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index ad4bd58..dff058c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -621,6 +621,51 @@
configurationListenerArgumentCaptor.value.onUiModeChanged()
verify(view).reloadColors()
}
+ @Test
+ fun onOrientationChanged_landscapeKeyguardFlagDisabled_blockReinflate() {
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+
+ // Run onOrientationChanged
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onOrientationChanged(
+ Configuration.ORIENTATION_LANDSCAPE
+ )
+ // Verify view is reinflated when flag is on
+ verify(viewFlipperController, never()).clearViews()
+ verify(viewFlipperController, never())
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ }
+
+ @Test
+ fun onOrientationChanged_landscapeKeyguardFlagEnabled_doesReinflate() {
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
+
+ // Run onOrientationChanged
+ val configurationListenerArgumentCaptor =
+ ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+ underTest.onViewAttached()
+ verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+ clearInvocations(viewFlipperController)
+ configurationListenerArgumentCaptor.value.onOrientationChanged(
+ Configuration.ORIENTATION_LANDSCAPE
+ )
+ // Verify view is reinflated when flag is on
+ verify(viewFlipperController).clearViews()
+ verify(viewFlipperController)
+ .asynchronouslyInflateView(
+ eq(SecurityMode.PIN),
+ any(),
+ onViewInflatedCallbackArgumentCaptor.capture()
+ )
+ }
@Test
fun hasDismissActions() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 7d23c80..b8b0198 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -77,7 +77,7 @@
public void updatePosition_primaryClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", false, true));
mController.updatePosition(10, 15, 20f, true);
@@ -92,7 +92,7 @@
public void updatePosition_alternateClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig(true, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", true, true));
mController.updatePosition(10, 15, 20f, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index dcaafe8..6fcf54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
@@ -47,7 +47,7 @@
@SmallTest
@RoboPilotTest
@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var underTest: LightRevealScrimRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index a3f7fc5..e0ae0c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -131,14 +131,12 @@
}
@Test
- fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+ fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_showsPrimaryBouncer() {
keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU)
}
@Test
@@ -147,42 +145,48 @@
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- // action down: does NOT collapse the shade
- val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
-
- // action up: collapses the shade
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
}
@Test
- fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+ fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_doNothing() {
keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
+ verifyActionsDoNothing(KeyEvent.KEYCODE_MENU)
}
@Test
- fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+ fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_showsPrimaryBouncer() {
keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
- // action down: does NOT collapse the shade
- val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
- assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
+ }
- // action up: collapses the shade
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ @Test
+ fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
+ keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER)
}
@Test
@@ -252,4 +256,42 @@
.isFalse()
verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
}
+
+ private fun verifyActionUpCollapsesTheShade(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
+ }
+
+ private fun verifyActionsDoNothing(keycode: Int) {
+ // action down: does nothing
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: doesNothing
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index ef38d54..3576ec9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -281,11 +281,7 @@
underTest.onPreviewSlotSelected(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
)
- keyguardInteractor.previewMode.value =
- KeyguardInteractor.PreviewMode(
- isInPreviewMode = true,
- shouldHighlightSelectedAffordance = true,
- )
+ underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
repository.setKeyguardShowing(false)
val latest = collectLastValue(underTest.startButton)
@@ -330,11 +326,7 @@
underTest.onPreviewSlotSelected(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
)
- keyguardInteractor.previewMode.value =
- KeyguardInteractor.PreviewMode(
- isInPreviewMode = true,
- shouldHighlightSelectedAffordance = true,
- )
+ underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
repository.setKeyguardShowing(false)
val endButton = collectLastValue(underTest.endButton)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 05e933b..a14a1c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -45,7 +45,6 @@
private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var keyguardInteractor: KeyguardInteractor
- @Mock private lateinit var keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
@Before
fun setUp() {
@@ -63,10 +62,7 @@
keyguardInteractor = withDeps.keyguardInteractor
repository = withDeps.repository
- underTest = KeyguardRootViewModel(
- keyguardInteractor,
- keyguardQuickAffordancesCombinedViewModel,
- )
+ underTest = KeyguardRootViewModel(keyguardInteractor)
}
@Test
@@ -89,10 +85,7 @@
fun alpha_inPreviewMode_doesNotChange() =
testScope.runTest {
val value = collectLastValue(underTest.alpha)
- underTest.enablePreviewMode(
- initiallySelectedSlotId = null,
- shouldHighlightSelectedAffordance = false,
- )
+ underTest.enablePreviewMode()
Truth.assertThat(value()).isEqualTo(1f)
repository.setKeyguardAlpha(0.1f)
@@ -104,4 +97,4 @@
repository.setKeyguardAlpha(0f)
Truth.assertThat(value()).isEqualTo(1f)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 9781baa..64d3b82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -53,7 +53,6 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginManager;
@@ -65,6 +64,7 @@
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
@@ -131,6 +131,8 @@
private FakeFeatureFlags mFeatureFlags;
+ private QSPipelineFlagsRepository mQSPipelineFlagsRepository;
+
private FakeExecutor mMainExecutor;
private QSTileHost mQSTileHost;
@@ -142,6 +144,7 @@
mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
+ mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags);
mMainExecutor = new FakeExecutor(new FakeSystemClock());
@@ -164,7 +167,7 @@
mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
+ mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
mMainExecutor.runAllReady();
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@@ -708,7 +711,7 @@
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager, FeatureFlags featureFlags) {
+ UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
super(context, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, shadeController, qsLogger,
userTracker, secureSettings, customTileStatePersister,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 6689514..54a9360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.qs.toProto
@@ -79,6 +80,7 @@
private val customTileAddedRepository: CustomTileAddedRepository =
FakeCustomTileAddedRepository()
private val featureFlags = FakeFeatureFlags()
+ private val pipelineFlags = QSPipelineFlagsRepository(featureFlags)
private val tileLifecycleManagerFactory = TLMFactory()
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@@ -120,7 +122,7 @@
backgroundDispatcher = testDispatcher,
scope = testScope.backgroundScope,
logger = logger,
- featureFlags = featureFlags,
+ featureFlags = pipelineFlags,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
new file mode 100644
index 0000000..489221e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -0,0 +1,61 @@
+package com.android.systemui.qs.pipeline.shared
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class QSPipelineFlagsRepositoryTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ """
+WHEN: qs_pipeline_new_host = {0}, qs_pipeline_auto_add = {1}
+THEN: pipelineNewHost = {2}, pipelineAutoAdd = {3}
+ """
+ )
+ @JvmStatic
+ fun data(): List<Array<Boolean>> =
+ (0 until 4).map { combination ->
+ val qs_pipeline_new_host = combination and 0b10 != 0
+ val qs_pipeline_auto_add = combination and 0b01 != 0
+ arrayOf(
+ qs_pipeline_new_host,
+ qs_pipeline_auto_add,
+ /* pipelineNewHost = */ qs_pipeline_new_host,
+ /* pipelineAutoAdd = */ qs_pipeline_new_host && qs_pipeline_auto_add,
+ )
+ }
+ }
+
+ @JvmField @Parameter(0) var qsPipelineNewHostFlag: Boolean = false
+ @JvmField @Parameter(1) var qsPipelineAutoAddFlag: Boolean = false
+ @JvmField @Parameter(2) var pipelineNewHostExpected: Boolean = false
+ @JvmField @Parameter(3) var pipelineAutoAddExpected: Boolean = false
+
+ private val featureFlags = FakeFeatureFlags()
+ private val pipelineFlags = QSPipelineFlagsRepository(featureFlags)
+
+ @Before
+ fun setUp() {
+ featureFlags.apply {
+ set(Flags.QS_PIPELINE_NEW_HOST, qsPipelineNewHostFlag)
+ set(Flags.QS_PIPELINE_AUTO_ADD, qsPipelineAutoAddFlag)
+ }
+ }
+
+ @Test
+ fun flagLogic() {
+ assertThat(pipelineFlags.pipelineHostEnabled).isEqualTo(pipelineNewHostExpected)
+ assertThat(pipelineFlags.pipelineAutoAddEnabled).isEqualTo(pipelineAutoAddExpected)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5cad9f8..981e44b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -73,7 +73,6 @@
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -120,7 +119,6 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -136,7 +134,6 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -167,17 +164,14 @@
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.user.domain.interactor.UserInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
@@ -364,16 +358,6 @@
mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
- mShadeRepository = new FakeShadeRepository();
- mShadeInteractor = new ShadeInteractor(
- TestScopeProvider.getTestScope(),
- mShadeRepository,
- new FakeDisableFlagsRepository(),
- new FakeKeyguardRepository(),
- new FakeUserSetupRepository(),
- mock(DeviceProvisionedController.class),
- mock(UserInteractor.class)
- );
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
@@ -600,33 +584,19 @@
mMainHandler,
mLayoutInflater,
mFeatureFlags,
- coordinator,
- expansionHandler,
- mDynamicPrivacyController,
- mKeyguardBypassController,
- mFalsingManager,
- new FalsingCollectorFake(),
+ coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
+ mFalsingManager, new FalsingCollectorFake(),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarWindowStateController,
mNotificationShadeWindowController,
- mDozeLog,
- mDozeParameters,
- mCommandQueue,
- mVibratorHelper,
- mLatencyTracker,
- mPowerManager,
- mAccessibilityManager,
- 0,
- mUpdateMonitor,
+ mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
+ mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger,
mShadeLog,
- mShadeInteractor,
mConfigurationController,
- () -> flingAnimationUtilsBuilder,
- mStatusBarTouchableRegionManager,
- mConversationNotificationManager,
- mMediaHierarchyManager,
+ () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+ mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
mGutsManager,
mNotificationsQSContainerController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index eb4ae1a..7aeafeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -539,6 +539,23 @@
}
@Test
+ public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() {
+ ArgumentCaptor<View.OnLayoutChangeListener> captor =
+ ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+ verify(mKeyguardStatusView).addOnLayoutChangeListener(captor.capture());
+ View.OnLayoutChangeListener listener = captor.getValue();
+
+ clearInvocations(mNotificationStackScrollLayoutController);
+
+ when(mKeyguardStatusView.getHeight()).thenReturn(0);
+ listener.onLayoutChange(mKeyguardStatusView, /* left= */ 0, /* top= */ 0, /* right= */
+ 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
+ 0, /* oldBottom = */ 200);
+
+ verify(mNotificationStackScrollLayoutController).animateNextTopPaddingChange();
+ }
+
+ @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 1edeeff..dc506a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.KeyEvent
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -118,6 +120,7 @@
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock
lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
+ @Mock lateinit var keyEventInteractor: KeyEventInteractor
private val notificationExpansionRepository = NotificationExpansionRepository()
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -188,7 +191,8 @@
CountDownTimerUtil(),
featureFlags
),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ keyEventInteractor,
)
underTest.setupExpandedStatusBar()
@@ -345,6 +349,27 @@
verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
}
+ @Test
+ fun forwardsDispatchKeyEvent() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ interactionEventHandler.dispatchKeyEvent(keyEvent)
+ verify(keyEventInteractor).dispatchKeyEvent(keyEvent)
+ }
+
+ @Test
+ fun forwardsDispatchKeyEventPreIme() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
+ verify(keyEventInteractor).dispatchKeyEventPreIme(keyEvent)
+ }
+
+ @Test
+ fun forwardsInterceptMediaKey() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ interactionEventHandler.interceptMediaKey(keyEvent)
+ verify(keyEventInteractor).interceptMediaKey(keyEvent)
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 829184c..66d48d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -198,7 +199,8 @@
CountDownTimerUtil(),
featureFlags
),
- BouncerLogger(logcatLogBuffer("BouncerLog"))
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ Mockito.mock(KeyEventInteractor::class.java),
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 35a54d1..a2c2912 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -49,7 +49,6 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -171,7 +170,6 @@
mShadeInteractor =
new ShadeInteractor(
mTestScope.getBackgroundScope(),
- new FakeShadeRepository(),
mDisableFlagsRepository,
mKeyguardRepository,
new FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 9e6c12f..cdcd1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -67,7 +67,6 @@
private val featureFlags = FakeFeatureFlags()
private val userSetupRepository = FakeUserSetupRepository()
private val userRepository = FakeUserRepository()
- private val shadeRepository = FakeShadeRepository()
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val keyguardRepository = FakeKeyguardRepository()
@@ -139,7 +138,6 @@
underTest =
ShadeInteractor(
testScope.backgroundScope,
- shadeRepository,
disableFlagsRepository,
keyguardRepository,
userSetupRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 9495fdd..ecaf137 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -227,6 +227,25 @@
}
@Test
+ fun activeClockId_changeAfterPluginConnected() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
+
+ registry.applySettings(ClockSettings("clock_3", null))
+
+ pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle)
+ assertEquals(DEFAULT_CLOCK_ID, registry.activeClockId)
+
+ pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
+ assertEquals("clock_3", registry.activeClockId)
+ }
+
+ @Test
fun createDefaultClock_pluginDisconnected() {
val plugin1 = FakeClockPlugin()
.addClock("clock_1", "clock 1")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index ec4367c..4a2518a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -102,10 +102,8 @@
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
private val disableFlagsRepository = FakeDisableFlagsRepository()
private val keyguardRepository = FakeKeyguardRepository()
- private val shadeRepository = FakeShadeRepository()
private val shadeInteractor = ShadeInteractor(
testScope.backgroundScope,
- shadeRepository,
disableFlagsRepository,
keyguardRepository,
userSetupRepository = FakeUserSetupRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
new file mode 100644
index 0000000..b8792a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
+ @Mock private lateinit var notifListener: NotificationListener
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var wakeUpCoordinator: NotificationWakeUpCoordinator
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+ @Mock private lateinit var notifMediaManager: NotificationMediaManager
+ @Mock private lateinit var dozeParams: DozeParameters
+ @Mock private lateinit var sectionStyleProvider: SectionStyleProvider
+ @Mock private lateinit var darkIconDispatcher: DarkIconDispatcher
+ @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var screenOffAnimController: ScreenOffAnimationController
+ @Mock private lateinit var bubbles: Bubbles
+ @Mock private lateinit var demoModeController: DemoModeController
+ @Mock private lateinit var aodIcons: NotificationIconContainer
+ @Mock private lateinit var featureFlags: FeatureFlags
+
+ private val shelfViewModel = NotificationIconContainerShelfViewModel()
+ private val statusBarViewModel = NotificationIconContainerStatusBarViewModel()
+ private val aodViewModel = NotificationIconContainerAlwaysOnDisplayViewModel()
+
+ private lateinit var underTest: NotificationIconAreaControllerViewBinderWrapperImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ NotificationIconAreaControllerViewBinderWrapperImpl(
+ mContext,
+ statusBarStateController,
+ wakeUpCoordinator,
+ keyguardBypassController,
+ notifMediaManager,
+ notifListener,
+ dozeParams,
+ sectionStyleProvider,
+ Optional.of(bubbles),
+ demoModeController,
+ darkIconDispatcher,
+ featureFlags,
+ statusBarWindowController,
+ screenOffAnimController,
+ shelfViewModel,
+ statusBarViewModel,
+ aodViewModel,
+ )
+ }
+
+ @Test
+ fun testNotificationIcons_settingHideIcons() {
+ underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
+ assertFalse(underTest.shouldShowLowPriorityIcons())
+ }
+
+ @Test
+ fun testNotificationIcons_settingShowIcons() {
+ underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
+ assertTrue(underTest.shouldShowLowPriorityIcons())
+ }
+
+ @Test
+ fun testAppearResetsTranslation() {
+ underTest.setupAodIcons(aodIcons)
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ underTest.appearAodIcons()
+ verify(aodIcons).translationY = 0f
+ verify(aodIcons).alpha = 1.0f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 8e1dcf0..1b8cfd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -48,7 +48,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationIconAreaControllerTest extends SysuiTestCase {
+public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
@Mock
private NotificationListener mListener;
@@ -70,7 +70,7 @@
StatusBarWindowController mStatusBarWindowController;
@Mock
ScreenOffAnimationController mScreenOffAnimationController;
- private NotificationIconAreaController mController;
+ private LegacyNotificationIconAreaControllerImpl mController;
@Mock
private Bubbles mBubbles;
@Mock private DemoModeController mDemoModeController;
@@ -82,7 +82,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mController = new NotificationIconAreaController(
+ mController = new LegacyNotificationIconAreaControllerImpl(
mContext,
mStatusBarStateController,
mWakeUpCoordinator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 0dc1d9a..6b3bd22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1802,6 +1802,15 @@
assertFalse(ScrimState.UNLOCKED.mAnimateChange);
}
+ @Test
+ public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.onUnlockAnimationFinished();
+ assertAlphaAfterExpansion(mNotificationsScrim, 1f, 1f);
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 5f0011b..fdc4f37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -32,7 +32,6 @@
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -500,123 +499,45 @@
@Test
public void ifPortraitHalfOpen_drawVerticallyTop() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0 , null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_PORTRAIT);
// Call show() to trigger layout updates before verifying position
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
public void ifPortraitAndOpen_drawCenterVertically() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_PORTRAIT);
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
public void ifLandscapeAndHalfOpen_drawCenterVertically() {
- DevicePostureController devicePostureController = mock(DevicePostureController.class);
- when(devicePostureController.getDevicePosture())
- .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
-
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- devicePostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
- verify(devicePostureController).addCallback(any());
- dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+ mDialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
mTestableLooper.processAllMessages(); // let dismiss() finish
setOrientation(Configuration.ORIENTATION_LANDSCAPE);
- dialog.show(SHOW_REASON_UNKNOWN);
+ mDialog.show(SHOW_REASON_UNKNOWN);
mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
- int gravity = dialog.getWindowGravity();
+ int gravity = mDialog.getWindowGravity();
assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
-
- cleanUp(dialog);
}
@Test
@@ -627,31 +548,9 @@
@Test
public void dialogDestroy_removesPostureControllerCallback() {
- VolumeDialogImpl dialog = new VolumeDialogImpl(
- getContext(),
- mVolumeDialogController,
- mAccessibilityMgr,
- mDeviceProvisionedController,
- mConfigurationController,
- mMediaOutputDialogFactory,
- mVolumePanelFactory,
- mActivityStarter,
- mInteractionJankMonitor,
- false,
- mCsdWarningDialogFactory,
- mPostureController,
- mTestableLooper.getLooper(),
- mDumpManager,
- mFeatureFlags
- );
- dialog.init(0, null);
-
verify(mPostureController, never()).removeCallback(any());
- dialog.destroy();
-
+ mDialog.destroy();
verify(mPostureController).removeCallback(any());
-
- cleanUp(dialog);
}
private void setOrientation(int orientation) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 35a3fd0..492e542 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -30,9 +30,6 @@
private val _qsExpansion = MutableStateFlow(0f)
override val qsExpansion = _qsExpansion
- private val _expansion = MutableStateFlow(0f)
- override val expansion = _expansion
-
private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
@@ -44,10 +41,6 @@
_qsExpansion.value = qsExpansion
}
- override fun setExpansion(expansion: Float) {
- _expansion.value = expansion
- }
-
override fun setUdfpsTransitionToFullShadeProgress(progress: Float) {
_udfpsTransitionToFullShadeProgress.value = progress
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ee6c28e..d1be5a9 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -43,6 +43,7 @@
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -125,6 +126,7 @@
private final VirtualDeviceManagerService mService;
private final PendingTrampolineCallback mPendingTrampolineCallback;
private final int mOwnerUid;
+ private final String mOwnerPackageName;
private int mDeviceId;
private @Nullable String mPersistentDeviceId;
// Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
@@ -196,7 +198,7 @@
AssociationInfo associationInfo,
VirtualDeviceManagerService service,
IBinder token,
- int ownerUid,
+ AttributionSource attributionSource,
int deviceId,
CameraAccessController cameraAccessController,
PendingTrampolineCallback pendingTrampolineCallback,
@@ -209,7 +211,7 @@
associationInfo,
service,
token,
- ownerUid,
+ attributionSource,
deviceId,
/* inputController= */ null,
cameraAccessController,
@@ -227,7 +229,7 @@
AssociationInfo associationInfo,
VirtualDeviceManagerService service,
IBinder token,
- int ownerUid,
+ AttributionSource attributionSource,
int deviceId,
InputController inputController,
CameraAccessController cameraAccessController,
@@ -238,7 +240,8 @@
VirtualDeviceParams params,
DisplayManagerGlobal displayManager) {
super(PermissionEnforcer.fromContext(context));
- UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
+ mOwnerPackageName = attributionSource.getPackageName();
+ UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId();
@@ -247,7 +250,7 @@
mActivityListener = activityListener;
mSoundEffectListener = soundEffectListener;
mRunningAppsChangedCallback = runningAppsChangedCallback;
- mOwnerUid = ownerUid;
+ mOwnerUid = attributionSource.getUid();
mDeviceId = deviceId;
mAppToken = token;
mParams = params;
@@ -771,7 +774,9 @@
fout.println(" VirtualDevice: ");
fout.println(" mDeviceId: " + mDeviceId);
fout.println(" mAssociationId: " + mAssociationInfo.getId());
- fout.println(" mParams: " + mParams);
+ fout.println(" mOwnerPackageName: " + mOwnerPackageName);
+ fout.println(" mParams: ");
+ mParams.dump(fout, " ");
fout.println(" mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
for (int i = 0; i < mVirtualDisplays.size(); i++) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 77508a8..e558498 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -35,6 +35,7 @@
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.sensor.VirtualSensor;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.IVirtualDisplayCallback;
@@ -314,13 +315,15 @@
@Override // Binder call
public IVirtualDevice createVirtualDevice(
IBinder token,
- String packageName,
+ AttributionSource attributionSource,
int associationId,
@NonNull VirtualDeviceParams params,
@NonNull IVirtualDeviceActivityListener activityListener,
@NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
createVirtualDevice_enforcePermission();
+ attributionSource.enforceCallingUid();
final int callingUid = getCallingUid();
+ final String packageName = attributionSource.getPackageName();
if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
throw new SecurityException(
"Package name " + packageName + " does not belong to calling uid "
@@ -340,10 +343,9 @@
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
- VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, VirtualDeviceManagerService.this, token, callingUid,
- deviceId, cameraAccessController,
- mPendingTrampolineCallback, activityListener,
+ VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), associationInfo,
+ VirtualDeviceManagerService.this, token, attributionSource, deviceId,
+ cameraAccessController, mPendingTrampolineCallback, activityListener,
soundEffectListener, runningAppsChangedCallback, params);
synchronized (mVirtualDeviceManagerLock) {
if (mVirtualDevices.size() == 0) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 315972c..f594170 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -1225,15 +1225,13 @@
public ContentCaptureOptions getOptions(@UserIdInt int userId,
@NonNull String packageName) {
boolean isContentCaptureReceiverEnabled;
- boolean isContentProtectionReceiverEnabled;
+ boolean isContentProtectionReceiverEnabled =
+ isContentProtectionReceiverEnabled(userId, packageName);
ArraySet<ComponentName> whitelistedComponents = null;
synchronized (mGlobalWhitelistStateLock) {
isContentCaptureReceiverEnabled =
isContentCaptureReceiverEnabled(userId, packageName);
- isContentProtectionReceiverEnabled =
- isContentProtectionReceiverEnabled(userId, packageName);
-
if (!isContentCaptureReceiverEnabled) {
// Full package is not allowlisted: check individual components next
whitelistedComponents = getWhitelistedComponents(userId, packageName);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index b67e627..d47a399 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -81,7 +81,7 @@
# when a notification has been clicked
27520 notification_clicked (key|3),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
# when a notification action button has been clicked
-27521 notification_action_clicked (key|3),(action_index|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
+27521 notification_action_clicked (key|3),(piIdentifier|3),(pendingIntent|3),(action_index|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1)
# when a notification has been canceled
27530 notification_canceled (key|3),(reason|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1),(count|1),(listener|3)
# replaces 27510 with a row per notification
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 0e4465d..1d09dce 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -87,7 +87,6 @@
DeviceConfig.NAMESPACE_CAMERA_NATIVE,
DeviceConfig.NAMESPACE_CONFIGURATION,
DeviceConfig.NAMESPACE_CONNECTIVITY,
- DeviceConfig.NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL,
DeviceConfig.NAMESPACE_EDGETPU_NATIVE,
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
@@ -115,19 +114,29 @@
NAMESPACE_TETHERING_U_OR_LATER_NATIVE
};
+ // All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
+ @VisibleForTesting
+ static final String[] sDeviceConfigAconfigScopes = new String[] {
+ DeviceConfig.NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL,
+ };
+
private final String[] mGlobalSettings;
private final String[] mDeviceConfigScopes;
+ private final String[] mDeviceConfigAconfigScopes;
+
private final ContentResolver mContentResolver;
@VisibleForTesting
protected SettingsToPropertiesMapper(ContentResolver contentResolver,
String[] globalSettings,
- String[] deviceConfigScopes) {
+ String[] deviceConfigScopes,
+ String[] deviceConfigAconfigScopes) {
mContentResolver = contentResolver;
mGlobalSettings = globalSettings;
mDeviceConfigScopes = deviceConfigScopes;
+ mDeviceConfigAconfigScopes = deviceConfigAconfigScopes;
}
@VisibleForTesting
@@ -173,6 +182,36 @@
return;
}
setProperty(propertyName, properties.getString(key, null));
+
+ // for legacy namespaces, they can also be used for trunk stable
+ // purposes. so push flag also into trunk stable slot in sys prop,
+ // later all legacy usage will be refactored and the sync to old
+ // sys prop slot can be removed.
+ String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
+ if (aconfigPropertyName == null) {
+ log("unable to construct system property for " + scope + "/"
+ + key);
+ return;
+ }
+ setProperty(aconfigPropertyName, properties.getString(key, null));
+ }
+ });
+ }
+
+ for (String deviceConfigAconfigScope : mDeviceConfigAconfigScopes) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ deviceConfigAconfigScope,
+ AsyncTask.THREAD_POOL_EXECUTOR,
+ (DeviceConfig.Properties properties) -> {
+ String scope = properties.getNamespace();
+ for (String key : properties.getKeyset()) {
+ String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
+ if (aconfigPropertyName == null) {
+ log("unable to construct system property for " + scope + "/"
+ + key);
+ return;
+ }
+ setProperty(aconfigPropertyName, properties.getString(key, null));
}
});
}
@@ -180,7 +219,10 @@
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper(
- contentResolver, sGlobalSettings, sDeviceConfigScopes);
+ contentResolver,
+ sGlobalSettings,
+ sDeviceConfigScopes,
+ sDeviceConfigAconfigScopes);
mapper.updatePropertiesFromSettings();
return mapper;
}
@@ -243,6 +285,28 @@
return propertyName;
}
+ /**
+ * system property name constructing rule for aconfig flags:
+ * "persist.device_config.aconfig_flags.[category_name].[flag_name]".
+ * If the name contains invalid characters or substrings for system property name,
+ * will return null.
+ * @param categoryName
+ * @param flagName
+ * @return
+ */
+ @VisibleForTesting
+ static String makeAconfigFlagPropertyName(String categoryName, String flagName) {
+ String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." +
+ categoryName + "." + flagName;
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ return null;
+ }
+
+ return propertyName;
+ }
+
private void setProperty(String key, String value) {
// Check if need to clear the property
if (value == null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d89171d..4e01997 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -391,7 +391,6 @@
final boolean wasBtScoRequested = isBluetoothScoRequested();
CommunicationRouteClient client;
-
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
boolean prevPrivileged = false;
@@ -1043,7 +1042,7 @@
synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOn = on;
updateAudioHalBluetoothState();
- postUpdateCommunicationRouteClient(eventSource);
+ postUpdateCommunicationRouteClient(isBluetoothScoRequested(), eventSource);
}
}
@@ -1395,8 +1394,10 @@
MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset);
}
- /*package*/ void postUpdateCommunicationRouteClient(String eventSource) {
- sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
+ /*package*/ void postUpdateCommunicationRouteClient(
+ boolean wasBtScoRequested, String eventSource) {
+ sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
+ wasBtScoRequested ? 1 : 0, eventSource);
}
/*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
@@ -1708,7 +1709,8 @@
: AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient("setBluetoothActiveDevice");
+ onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ "setBluetoothActiveDevice");
}
}
}
@@ -1762,9 +1764,11 @@
case MSG_I_SET_MODE_OWNER:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
+ boolean wasBtScoRequested = isBluetoothScoRequested();
mAudioModeOwner = (AudioModeInfo) msg.obj;
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
- onUpdateCommunicationRouteClient("setNewModeOwner");
+ onUpdateCommunicationRouteClient(
+ wasBtScoRequested, "setNewModeOwner");
}
}
}
@@ -1787,10 +1791,10 @@
}
break;
- case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT:
+ case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- onUpdateCommunicationRouteClient((String) msg.obj);
+ onUpdateCommunicationRouteClient(msg.arg1 == 1, (String) msg.obj);
}
}
break;
@@ -1971,7 +1975,7 @@
private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
- private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
+ private static final int MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
@@ -2328,16 +2332,20 @@
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void onUpdateCommunicationRouteClient(String eventSource) {
- updateCommunicationRoute(eventSource);
+ private void onUpdateCommunicationRouteClient(boolean wasBtScoRequested, String eventSource) {
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
- + crc + " eventSource: " + eventSource);
+ Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
+ + " wasBtScoRequested: " + wasBtScoRequested + " eventSource: " + eventSource);
}
if (crc != null) {
setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
+ } else {
+ if (!isBluetoothScoRequested() && wasBtScoRequested) {
+ mBtHelper.stopBluetoothSco(eventSource);
+ }
+ updateCommunicationRoute(eventSource);
}
}
@@ -2431,6 +2439,7 @@
List<AudioRecordingConfiguration> recordConfigs) {
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
+ final boolean wasBtScoRequested = isBluetoothScoRequested();
boolean updateCommunicationRoute = false;
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
boolean wasActive = crc.isActive();
@@ -2459,7 +2468,8 @@
}
}
if (updateCommunicationRoute) {
- postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+ postUpdateCommunicationRouteClient(
+ wasBtScoRequested, "updateCommunicationRouteClientsActivity");
}
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index aaf5292..b350363 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -329,7 +329,7 @@
default:
break;
}
- if(broadcast) {
+ if (broadcast) {
broadcastScoConnectionState(scoAudioState);
//FIXME: this is to maintain compatibility with deprecated intent
// AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
@@ -723,8 +723,10 @@
checkScoAudioState();
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // the connection except if already connected internally
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL) {
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ }
switch (mScoAudioState) {
case SCO_STATE_INACTIVE:
mScoAudioMode = scoAudioMode;
@@ -780,7 +782,7 @@
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
case SCO_STATE_ACTIVE_INTERNAL:
- Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
+ // Already in ACTIVE mode, simply return
break;
case SCO_STATE_ACTIVE_EXTERNAL:
/* Confirm SCO Audio connection to requesting app as it is already connected
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d57dc47..8642fb8 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -16,6 +16,9 @@
package com.android.server.display;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
@@ -132,12 +135,15 @@
/**
* Returns the default size of the surface associated with the display, or null if the surface
* is not provided for layer mirroring by SurfaceFlinger. For non virtual displays, this will
- * be the actual display device's size.
+ * be the actual display device's size, reflecting the current rotation.
*/
@Nullable
public Point getDisplaySurfaceDefaultSizeLocked() {
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
- return new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ final boolean isRotated = mCurrentOrientation == ROTATION_90
+ || mCurrentOrientation == ROTATION_270;
+ return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
+ : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
}
/**
@@ -358,7 +364,7 @@
}
boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
- || mCurrentOrientation == Surface.ROTATION_270);
+ || mCurrentOrientation == ROTATION_270);
DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
viewport.deviceWidth = isRotated ? info.height : info.width;
viewport.deviceHeight = isRotated ? info.width : info.height;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c131226..01eceda 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2093,7 +2093,7 @@
/** Loads the refresh rate profiles. */
private void loadRefreshRateZoneProfiles(RefreshRateConfigs refreshRateConfigs) {
- if (refreshRateConfigs == null) {
+ if (refreshRateConfigs == null || refreshRateConfigs.getRefreshRateZoneProfiles() == null) {
return;
}
for (RefreshRateZone zone :
diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java
index 02e0eb0..4b8fabde 100644
--- a/services/core/java/com/android/server/input/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/FocusEventDebugView.java
@@ -24,11 +24,13 @@
import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
import android.graphics.Typeface;
-import android.util.Log;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@@ -38,22 +40,29 @@
import android.view.MotionEvent;
import android.view.RoundedCorner;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
/**
* Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
* the screen.
*/
-class FocusEventDebugView extends LinearLayout {
+class FocusEventDebugView extends RelativeLayout {
private static final String TAG = FocusEventDebugView.class.getSimpleName();
@@ -66,9 +75,11 @@
private static final int KEY_VIEW_VERTICAL_PADDING_DP = 8;
private static final int KEY_VIEW_MIN_WIDTH_DP = 32;
private static final int KEY_VIEW_TEXT_SIZE_SP = 12;
+ private static final double ROTATY_GRAPH_HEIGHT_FRACTION = 0.5;
private final InputManagerService mService;
private final int mOuterPadding;
+ private final DisplayMetrics mDm;
// Tracks all keys that are currently pressed/down.
private final Map<Pair<Integer /*deviceId*/, Integer /*scanCode*/>, PressedKeyView>
@@ -80,18 +91,29 @@
private PressedKeyContainer mPressedKeyContainer;
@Nullable
private PressedKeyContainer mPressedModifierContainer;
+ private final Supplier<RotaryInputValueView> mRotaryInputValueViewFactory;
+ @Nullable
+ private RotaryInputValueView mRotaryInputValueView;
+ private final Supplier<RotaryInputGraphView> mRotaryInputGraphViewFactory;
+ @Nullable
+ private RotaryInputGraphView mRotaryInputGraphView;
- FocusEventDebugView(Context c, InputManagerService service) {
+ @VisibleForTesting
+ FocusEventDebugView(Context c, InputManagerService service,
+ Supplier<RotaryInputValueView> rotaryInputValueViewFactory,
+ Supplier<RotaryInputGraphView> rotaryInputGraphViewFactory) {
super(c);
setFocusableInTouchMode(true);
mService = service;
- final var dm = mContext.getResources().getDisplayMetrics();
- mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm);
+ mRotaryInputValueViewFactory = rotaryInputValueViewFactory;
+ mRotaryInputGraphViewFactory = rotaryInputGraphViewFactory;
+ mDm = mContext.getResources().getDisplayMetrics();
+ mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, mDm);
+ }
- setOrientation(HORIZONTAL);
- setLayoutDirection(LAYOUT_DIRECTION_RTL);
- setGravity(Gravity.START | Gravity.BOTTOM);
+ FocusEventDebugView(Context c, InputManagerService service) {
+ this(c, service, () -> new RotaryInputValueView(c), () -> new RotaryInputGraphView(c));
}
@Override
@@ -100,13 +122,13 @@
final RoundedCorner bottomLeft =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
- if (bottomLeft != null) {
+ if (bottomLeft != null && !insets.isRound()) {
paddingBottom = bottomLeft.getRadius();
}
final RoundedCorner bottomRight =
insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
- if (bottomRight != null) {
+ if (bottomRight != null && !insets.isRound()) {
paddingBottom = Math.max(paddingBottom, bottomRight.getRadius());
}
@@ -151,7 +173,7 @@
}
mPressedKeyContainer = new PressedKeyContainer(mContext);
- mPressedKeyContainer.setOrientation(HORIZONTAL);
+ mPressedKeyContainer.setOrientation(LinearLayout.HORIZONTAL);
mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
final var scroller = new HorizontalScrollView(mContext);
@@ -160,15 +182,23 @@
scroller.addOnLayoutChangeListener(
(view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
scroller.setHorizontalFadingEdgeEnabled(true);
- addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));
+ LayoutParams scrollerLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ scrollerLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ scrollerLayoutParams.addRule(ALIGN_PARENT_RIGHT);
+ addView(scroller, scrollerLayoutParams);
mPressedModifierContainer = new PressedKeyContainer(mContext);
- mPressedModifierContainer.setOrientation(VERTICAL);
+ mPressedModifierContainer.setOrientation(LinearLayout.VERTICAL);
mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
- addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ LayoutParams modifierLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ modifierLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ modifierLayoutParams.addRule(ALIGN_PARENT_LEFT);
+ modifierLayoutParams.addRule(LEFT_OF, scroller.getId());
+ addView(mPressedModifierContainer, modifierLayoutParams);
}
- private void handleUpdateShowRotaryInput(boolean enabled) {
+ @VisibleForTesting
+ void handleUpdateShowRotaryInput(boolean enabled) {
if (enabled == showRotaryInput()) {
return;
}
@@ -176,10 +206,26 @@
if (!enabled) {
mFocusEventDebugGlobalMonitor.dispose();
mFocusEventDebugGlobalMonitor = null;
+ removeView(mRotaryInputValueView);
+ mRotaryInputValueView = null;
+ removeView(mRotaryInputGraphView);
+ mRotaryInputGraphView = null;
return;
}
mFocusEventDebugGlobalMonitor = new FocusEventDebugGlobalMonitor(this, mService);
+
+ mRotaryInputValueView = mRotaryInputValueViewFactory.get();
+ LayoutParams valueLayoutParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ valueLayoutParams.addRule(CENTER_HORIZONTAL);
+ valueLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
+ addView(mRotaryInputValueView, valueLayoutParams);
+
+ mRotaryInputGraphView = mRotaryInputGraphViewFactory.get();
+ LayoutParams graphLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ (int) (ROTATY_GRAPH_HEIGHT_FRACTION * mDm.heightPixels));
+ graphLayoutParams.addRule(CENTER_IN_PARENT);
+ addView(mRotaryInputGraphView, graphLayoutParams);
}
/** Report a key event to the debug view. */
@@ -242,14 +288,15 @@
keyEvent.recycle();
}
- private void handleRotaryInput(MotionEvent motionEvent) {
+ @VisibleForTesting
+ void handleRotaryInput(MotionEvent motionEvent) {
if (!showRotaryInput()) {
return;
}
float scrollAxisValue = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
- // TODO(b/286086154): replace log with visualization.
- Log.d(TAG, "ROTARY INPUT: " + String.valueOf(scrollAxisValue));
+ mRotaryInputValueView.updateValue(scrollAxisValue);
+ mRotaryInputGraphView.addValue(scrollAxisValue, motionEvent.getEventTime());
motionEvent.recycle();
}
@@ -308,7 +355,14 @@
/** Determine whether to show rotary input by checking one of the rotary-related objects. */
private boolean showRotaryInput() {
- return mFocusEventDebugGlobalMonitor != null;
+ return mRotaryInputValueView != null;
+ }
+
+ /**
+ * Converts a dimension in scaled pixel units to integer display pixels.
+ */
+ private static int applyDimensionSp(int dimensionSp, DisplayMetrics dm) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, dimensionSp, dm);
}
private static class PressedKeyView extends TextView {
@@ -419,4 +473,376 @@
invalidate();
}
}
+
+ // TODO(b/286086154): move RotaryInputGraphView and RotaryInputValueView to a subpackage.
+
+ /** Draws the most recent rotary input value and indicates whether the source is active. */
+ @VisibleForTesting
+ static class RotaryInputValueView extends TextView {
+
+ private static final int INACTIVE_TEXT_COLOR = 0xffff00ff;
+ private static final int ACTIVE_TEXT_COLOR = 0xff420f28;
+ private static final int TEXT_SIZE_SP = 8;
+ private static final int SIDE_PADDING_SP = 4;
+ /** Determines how long the active status lasts. */
+ private static final int ACTIVE_STATUS_DURATION = 250 /* milliseconds */;
+ private static final ColorFilter ACTIVE_BACKGROUND_FILTER =
+ new ColorMatrixColorFilter(new float[]{
+ 0, 0, 0, 0, 255, // red
+ 0, 0, 0, 0, 0, // green
+ 0, 0, 0, 0, 255, // blue
+ 0, 0, 0, 0, 200 // alpha
+ });
+
+ private final Runnable mUpdateActivityStatusCallback = () -> updateActivityStatus(false);
+ private final float mScaledVerticalScrollFactor;
+
+ @VisibleForTesting
+ RotaryInputValueView(Context c) {
+ super(c);
+
+ DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+ mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();
+
+ setText(getFormattedValue(0));
+ setTextColor(INACTIVE_TEXT_COLOR);
+ setTextSize(applyDimensionSp(TEXT_SIZE_SP, dm));
+ setPaddingRelative(applyDimensionSp(SIDE_PADDING_SP, dm), 0,
+ applyDimensionSp(SIDE_PADDING_SP, dm), 0);
+ setTypeface(null, Typeface.BOLD);
+ setBackgroundResource(R.drawable.focus_event_rotary_input_background);
+ }
+
+ void updateValue(float value) {
+ removeCallbacks(mUpdateActivityStatusCallback);
+
+ setText(getFormattedValue(value * mScaledVerticalScrollFactor));
+
+ updateActivityStatus(true);
+ postDelayed(mUpdateActivityStatusCallback, ACTIVE_STATUS_DURATION);
+ }
+
+ @VisibleForTesting
+ void updateActivityStatus(boolean active) {
+ if (active) {
+ setTextColor(ACTIVE_TEXT_COLOR);
+ getBackground().setColorFilter(ACTIVE_BACKGROUND_FILTER);
+ } else {
+ setTextColor(INACTIVE_TEXT_COLOR);
+ getBackground().clearColorFilter();
+ }
+ }
+
+ private static String getFormattedValue(float value) {
+ return String.format("%s%.1f", value < 0 ? "-" : "+", Math.abs(value));
+ }
+ }
+
+ /**
+ * Shows a graph with the rotary input values as a function of time.
+ * The graph gets reset if no action is received for a certain amount of time.
+ */
+ @VisibleForTesting
+ static class RotaryInputGraphView extends View {
+
+ private static final int FRAME_COLOR = 0xbf741b47;
+ private static final int FRAME_WIDTH_SP = 2;
+ private static final int FRAME_BORDER_GAP_SP = 10;
+ private static final int FRAME_TEXT_SIZE_SP = 10;
+ private static final int FRAME_TEXT_OFFSET_SP = 2;
+ private static final int GRAPH_COLOR = 0xffff00ff;
+ private static final int GRAPH_LINE_WIDTH_SP = 1;
+ private static final int GRAPH_POINT_RADIUS_SP = 4;
+ private static final long MAX_SHOWN_TIME_INTERVAL = TimeUnit.SECONDS.toMillis(5);
+ private static final float DEFAULT_FRAME_CENTER_POSITION = 0;
+ private static final int MAX_GRAPH_VALUES_SIZE = 400;
+ /** Maximum time between values so that they are considered part of the same gesture. */
+ private static final long MAX_GESTURE_TIME = TimeUnit.SECONDS.toMillis(1);
+
+ private final DisplayMetrics mDm;
+ /**
+ * Distance in position units (amount scrolled in display pixels) from the center to the
+ * top/bottom frame lines.
+ */
+ private final float mFrameCenterToBorderDistance;
+ private final float mScaledVerticalScrollFactor;
+ private final Locale mDefaultLocale;
+ private final Paint mFramePaint = new Paint();
+ private final Paint mFrameTextPaint = new Paint();
+ private final Paint mGraphLinePaint = new Paint();
+ private final Paint mGraphPointPaint = new Paint();
+
+ private final CyclicBuffer mGraphValues = new CyclicBuffer(MAX_GRAPH_VALUES_SIZE);
+ /** Position at which graph values are placed at the center of the graph. */
+ private float mFrameCenterPosition = DEFAULT_FRAME_CENTER_POSITION;
+
+ @VisibleForTesting
+ RotaryInputGraphView(Context c) {
+ super(c);
+
+ mDm = mContext.getResources().getDisplayMetrics();
+ // This makes the center-to-border distance equivalent to the display height, meaning
+ // that the total height of the graph is equivalent to 2x the display height.
+ mFrameCenterToBorderDistance = mDm.heightPixels;
+ mScaledVerticalScrollFactor = ViewConfiguration.get(c).getScaledVerticalScrollFactor();
+ mDefaultLocale = Locale.getDefault();
+
+ mFramePaint.setColor(FRAME_COLOR);
+ mFramePaint.setStrokeWidth(applyDimensionSp(FRAME_WIDTH_SP, mDm));
+
+ mFrameTextPaint.setColor(GRAPH_COLOR);
+ mFrameTextPaint.setTextSize(applyDimensionSp(FRAME_TEXT_SIZE_SP, mDm));
+
+ mGraphLinePaint.setColor(GRAPH_COLOR);
+ mGraphLinePaint.setStrokeWidth(applyDimensionSp(GRAPH_LINE_WIDTH_SP, mDm));
+ mGraphLinePaint.setStrokeCap(Paint.Cap.ROUND);
+ mGraphLinePaint.setStrokeJoin(Paint.Join.ROUND);
+
+ mGraphPointPaint.setColor(GRAPH_COLOR);
+ mGraphPointPaint.setStrokeWidth(applyDimensionSp(GRAPH_POINT_RADIUS_SP, mDm));
+ mGraphPointPaint.setStrokeCap(Paint.Cap.ROUND);
+ mGraphPointPaint.setStrokeJoin(Paint.Join.ROUND);
+ }
+
+ /**
+ * Reads new scroll axis value and updates the list accordingly. Old positions are
+ * kept at the front (what you would get with getFirst), while the recent positions are
+ * kept at the back (what you would get with getLast). Also updates the frame center
+ * position to handle out-of-bounds cases.
+ */
+ void addValue(float scrollAxisValue, long eventTime) {
+ // Remove values that are too old.
+ while (mGraphValues.getSize() > 0
+ && (eventTime - mGraphValues.getFirst().mTime) > MAX_SHOWN_TIME_INTERVAL) {
+ mGraphValues.removeFirst();
+ }
+
+ // If there are no recent values, reset the frame center.
+ if (mGraphValues.getSize() == 0) {
+ mFrameCenterPosition = DEFAULT_FRAME_CENTER_POSITION;
+ }
+
+ // Handle new value. We multiply the scroll axis value by the scaled scroll factor to
+ // get the amount of pixels to be scrolled. We also compute the accumulated position
+ // by adding the current value to the last one (if not empty).
+ final float displacement = scrollAxisValue * mScaledVerticalScrollFactor;
+ final float prevPos = (mGraphValues.getSize() == 0 ? 0 : mGraphValues.getLast().mPos);
+ final float pos = prevPos + displacement;
+
+ mGraphValues.add(pos, eventTime);
+
+ // The difference between the distance of the most recent position from the center
+ // frame (pos - mFrameCenterPosition) and the maximum allowed distance from the center
+ // frame (mFrameCenterToBorderDistance).
+ final float verticalDiff = Math.abs(pos - mFrameCenterPosition)
+ - mFrameCenterToBorderDistance;
+ // If needed, translate frame.
+ if (verticalDiff > 0) {
+ final int sign = pos - mFrameCenterPosition < 0 ? -1 : 1;
+ // Here, we update the center frame position by the exact amount needed for us to
+ // stay within the maximum allowed distance from the center frame.
+ mFrameCenterPosition += sign * verticalDiff;
+ }
+
+ // Redraw canvas.
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Note: vertical coordinates in Canvas go from top to bottom,
+ // that is bottomY > middleY > topY.
+ final int verticalMargin = applyDimensionSp(FRAME_BORDER_GAP_SP, mDm);
+ final int topY = verticalMargin;
+ final int bottomY = getHeight() - verticalMargin;
+ final int middleY = (topY + bottomY) / 2;
+
+ // Note: horizontal coordinates in Canvas go from left to right,
+ // that is rightX > leftX.
+ final int leftX = 0;
+ final int rightX = getWidth();
+
+ // Draw the frame, which includes 3 lines that show the maximum,
+ // minimum and middle positions of the graph.
+ canvas.drawLine(leftX, topY, rightX, topY, mFramePaint);
+ canvas.drawLine(leftX, middleY, rightX, middleY, mFramePaint);
+ canvas.drawLine(leftX, bottomY, rightX, bottomY, mFramePaint);
+
+ // Draw the position that each frame line corresponds to.
+ final int frameTextOffset = applyDimensionSp(FRAME_TEXT_OFFSET_SP, mDm);
+ canvas.drawText(
+ String.format(mDefaultLocale, "%.1f",
+ mFrameCenterPosition + mFrameCenterToBorderDistance),
+ leftX,
+ topY - frameTextOffset, mFrameTextPaint
+ );
+ canvas.drawText(
+ String.format(mDefaultLocale, "%.1f", mFrameCenterPosition),
+ leftX,
+ middleY - frameTextOffset, mFrameTextPaint
+ );
+ canvas.drawText(
+ String.format(mDefaultLocale, "%.1f",
+ mFrameCenterPosition - mFrameCenterToBorderDistance),
+ leftX,
+ bottomY - frameTextOffset, mFrameTextPaint
+ );
+
+ // If there are no graph values to be drawn, stop here.
+ if (mGraphValues.getSize() == 0) {
+ return;
+ }
+
+ // Draw the graph using the times and positions.
+ // We start at the most recent value (which should be drawn at the right) and move
+ // to the older values (which should be drawn to the left of more recent ones). Negative
+ // indices are handled by circuling back to the end of the buffer.
+ final long mostRecentTime = mGraphValues.getLast().mTime;
+ float prevCoordX = 0;
+ float prevCoordY = 0;
+ float prevAge = 0;
+ for (Iterator<GraphValue> iter = mGraphValues.reverseIterator(); iter.hasNext();) {
+ final GraphValue value = iter.next();
+
+ final int age = (int) (mostRecentTime - value.mTime);
+ final float pos = value.mPos;
+
+ // We get the horizontal coordinate in time units from left to right with
+ // (MAX_SHOWN_TIME_INTERVAL - age). Then, we rescale it to match the canvas
+ // units by dividing it by the time-domain length (MAX_SHOWN_TIME_INTERVAL)
+ // and by multiplying it by the canvas length (rightX - leftX). Finally, we
+ // offset the coordinate by adding it to leftX.
+ final float coordX = leftX + ((float) (MAX_SHOWN_TIME_INTERVAL - age)
+ / MAX_SHOWN_TIME_INTERVAL) * (rightX - leftX);
+
+ // We get the vertical coordinate in position units from middle to top with
+ // (pos - mFrameCenterPosition). Then, we rescale it to match the canvas
+ // units by dividing it by half of the position-domain length
+ // (mFrameCenterToBorderDistance) and by multiplying it by half of the canvas
+ // length (middleY - topY). Finally, we offset the coordinate by subtracting
+ // it from middleY (we can't "add" here because the coordinate grows from top
+ // to bottom).
+ final float coordY = middleY - ((pos - mFrameCenterPosition)
+ / mFrameCenterToBorderDistance) * (middleY - topY);
+
+ // Draw a point for this value.
+ canvas.drawPoint(coordX, coordY, mGraphPointPaint);
+
+ // If this value is part of the same gesture as the previous one, draw a line
+ // between them. We ignore the first value (with age = 0).
+ if (age != 0 && (age - prevAge) <= MAX_GESTURE_TIME) {
+ canvas.drawLine(prevCoordX, prevCoordY, coordX, coordY, mGraphLinePaint);
+ }
+
+ prevCoordX = coordX;
+ prevCoordY = coordY;
+ prevAge = age;
+ }
+ }
+
+ @VisibleForTesting
+ float getFrameCenterPosition() {
+ return mFrameCenterPosition;
+ }
+
+ /**
+ * Holds data needed to draw each entry in the graph.
+ */
+ private static class GraphValue {
+ /** Position. */
+ float mPos;
+ /** Time when this value was added. */
+ long mTime;
+
+ GraphValue(float pos, long time) {
+ this.mPos = pos;
+ this.mTime = time;
+ }
+ }
+
+ /**
+ * Holds the graph values as a cyclic buffer. It has a fixed capacity, and it replaces the
+ * old values with new ones to avoid creating new objects.
+ */
+ private static class CyclicBuffer {
+ private final GraphValue[] mValues;
+ private final int mCapacity;
+ private int mSize = 0;
+ private int mLastIndex = 0;
+
+ // The iteration index and counter are here to make it easier to reset them.
+ /** Determines the value currently pointed by the iterator. */
+ private int mIteratorIndex;
+ /** Counts how many values have been iterated through. */
+ private int mIteratorCount;
+
+ /** Used traverse the values in reverse order. */
+ private final Iterator<GraphValue> mReverseIterator = new Iterator<GraphValue>() {
+ @Override
+ public boolean hasNext() {
+ return mIteratorCount <= mSize;
+ }
+
+ @Override
+ public GraphValue next() {
+ // Returns the value currently pointed by the iterator and moves the iterator to
+ // the previous one.
+ mIteratorCount++;
+ return mValues[(mIteratorIndex-- + mCapacity) % mCapacity];
+ }
+ };
+
+ CyclicBuffer(int capacity) {
+ mCapacity = capacity;
+ mValues = new GraphValue[capacity];
+ }
+
+ /**
+ * Add new graph value. If there is an existing object, we replace its data with the
+ * new one. With this, we re-use old objects instead of creating new ones.
+ */
+ void add(float pos, long time) {
+ mLastIndex = (mLastIndex + 1) % mCapacity;
+ if (mValues[mLastIndex] == null) {
+ mValues[mLastIndex] = new GraphValue(pos, time);
+ } else {
+ final GraphValue oldValue = mValues[mLastIndex];
+ oldValue.mPos = pos;
+ oldValue.mTime = time;
+ }
+
+ // If needed, account for new value in the buffer size.
+ if (mSize != mCapacity) {
+ mSize++;
+ }
+ }
+
+ int getSize() {
+ return mSize;
+ }
+
+ GraphValue getFirst() {
+ final int distanceBetweenLastAndFirst = (mCapacity - mSize) + 1;
+ final int firstIndex = (mLastIndex + distanceBetweenLastAndFirst) % mCapacity;
+ return mValues[firstIndex];
+ }
+
+ GraphValue getLast() {
+ return mValues[mLastIndex];
+ }
+
+ void removeFirst() {
+ mSize--;
+ }
+
+ /** Returns an iterator pointing at the last value. */
+ Iterator<GraphValue> reverseIterator() {
+ mIteratorIndex = mLastIndex;
+ mIteratorCount = 1;
+ return mReverseIterator;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e782ea9..009cc3b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1239,7 +1239,9 @@
mNotificationRecordLogger.log(
NotificationRecordLogger.NotificationEvent.fromAction(actionIndex,
generatedByAssistant, action.isContextual()), r);
- EventLogTags.writeNotificationActionClicked(key, actionIndex,
+ EventLogTags.writeNotificationActionClicked(key,
+ action.actionIntent.getTarget().toString(),
+ action.actionIntent.getIntent().toString(), actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
nv.recycle();
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 6e75605..f987629 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -487,6 +487,8 @@
boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
+
boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
@NonNull
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7d878ec..1cfc7d7 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4925,8 +4925,8 @@
}
}
- @Override
- public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+ private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName,
+ int userId) {
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "isPackageSuspendedForUser for user " + userId);
@@ -4934,7 +4934,17 @@
if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
throw new IllegalArgumentException("Unknown target package: " + packageName);
}
- return ps.getUserStateOrDefault(userId).isSuspended();
+ return ps.getUserStateOrDefault(userId);
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
+ return getUserStageOrDefaultForUser(packageName, userId).isSuspended();
+ }
+
+ @Override
+ public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) {
+ return getUserStageOrDefaultForUser(packageName, userId).isQuarantined();
}
@Override
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index fd47846..76203ac 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -955,6 +955,13 @@
@Override
@Deprecated
+ public final boolean isPackageQuarantinedForUser(@NonNull String packageName,
+ @UserIdInt int userId) {
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ }
+
+ @Override
+ @Deprecated
public final boolean isSafeMode() {
// allow instant applications
return mService.getSafeMode();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 303f321..6270655 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -68,6 +68,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
@@ -312,6 +313,19 @@
private static final long SILENT_INSTALL_ALLOWED = 265131695L;
/**
+ * The system supports pre-approval and update ownership features from
+ * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34}. The change id is used to make sure
+ * the system includes the fix of pre-approval with update ownership case. When checking the
+ * change id, if it is disabled, it means the build includes the fix. The more detail is on
+ * b/293644536.
+ * See {@link PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)} and
+ * {@link #requestUserPreapproval(PreapprovalDetails, IntentSender)} for more details.
+ */
+ @Disabled
+ @ChangeId
+ private static final long PRE_APPROVAL_WITH_UPDATE_OWNERSHIP_FIX = 293644536L;
+
+ /**
* The default value of {@link #mValidatedTargetSdk} is {@link Integer#MAX_VALUE}. If {@link
* #mValidatedTargetSdk} is compared with {@link Build.VERSION_CODES#S} before getting the
* target sdk version from a validated apk in {@link #validateApkInstallLocked()}, the compared
@@ -927,16 +941,27 @@
if (mPermissionsManuallyAccepted) {
return USER_ACTION_NOT_NEEDED;
}
- packageName = mPackageName;
+ // For pre-pappvoal case, the mPackageName would be null.
+ if (mPackageName != null) {
+ packageName = mPackageName;
+ } else if (mPreapprovalRequested.get() && mPreapprovalDetails != null) {
+ packageName = mPreapprovalDetails.getPackageName();
+ } else {
+ packageName = null;
+ }
hasDeviceAdminReceiver = mHasDeviceAdminReceiver;
}
- final boolean forcePermissionPrompt =
+ // For the below cases, force user action prompt
+ // 1. installFlags includes INSTALL_FORCE_PERMISSION_PROMPT
+ // 2. params.requireUserAction is USER_ACTION_REQUIRED
+ final boolean forceUserActionPrompt =
(params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0
|| params.requireUserAction == SessionParams.USER_ACTION_REQUIRED;
- if (forcePermissionPrompt) {
- return USER_ACTION_REQUIRED;
- }
+ final int userActionNotTypicallyNeededResponse = forceUserActionPrompt
+ ? USER_ACTION_REQUIRED
+ : USER_ACTION_NOT_NEEDED;
+
// It is safe to access mInstallerUid and mInstallSource without lock
// because they are immutable after sealing.
final Computer snapshot = mPm.snapshotComputer();
@@ -990,7 +1015,7 @@
|| isInstallerDeviceOwnerOrAffiliatedProfileOwner();
if (noUserActionNecessary) {
- return USER_ACTION_NOT_NEEDED;
+ return userActionNotTypicallyNeededResponse;
}
if (isUpdateOwnershipEnforcementEnabled
@@ -1003,7 +1028,7 @@
}
if (isPermissionGranted) {
- return USER_ACTION_NOT_NEEDED;
+ return userActionNotTypicallyNeededResponse;
}
if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 6efd067..651845e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -755,9 +755,7 @@
@Override
public boolean isPackageQuarantined(@NonNull String packageName,
@UserIdInt int userId) {
- final PackageStateInternal packageState = getPackageStateInternal(packageName);
- return (packageState == null) ? false
- : packageState.getUserStateOrDefault(userId).isQuarantined();
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ceae1fe..8bdbe04 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -226,6 +226,8 @@
return runPath();
case "dump":
return runDump();
+ case "dump-package":
+ return runDumpPackage();
case "list":
return runList();
case "gc":
@@ -978,6 +980,7 @@
boolean listInstaller = false;
boolean showUid = false;
boolean showVersionCode = false;
+ boolean listQuarantinedOnly = false;
boolean listApexOnly = false;
boolean showStopped = false;
int uid = -1;
@@ -1008,6 +1011,9 @@
case "-s":
listSystem = true;
break;
+ case "-q":
+ listQuarantinedOnly = true;
+ break;
case "-U":
showUid = true;
break;
@@ -1093,6 +1099,10 @@
|| (listApexOnly && !isApex)) {
continue;
}
+ if (listQuarantinedOnly && !mInterface.isPackageQuarantinedForUser(info.packageName,
+ translatedUserId)) {
+ continue;
+ }
String name = null;
if (showSdks) {
@@ -3598,6 +3608,23 @@
return 0;
}
+ private int runDumpPackage() {
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ try {
+ ((IBinder) mInterface).dump(getOutFileDescriptor(), new String[]{pkg});
+ } catch (Throwable e) {
+ PrintWriter pw = getErrPrintWriter();
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ pw.flush();
+ }
+ return 0;
+ }
+
private int runSetHarmfulAppWarning() throws RemoteException {
int userId = UserHandle.USER_CURRENT;
@@ -4282,6 +4309,9 @@
pw.println(" dump PACKAGE");
pw.println(" Print various system state associated with the given PACKAGE.");
pw.println("");
+ pw.println(" dump-package PACKAGE");
+ pw.println(" Print package manager state associated with the given PACKAGE.");
+ pw.println("");
pw.println(" has-feature FEATURE_NAME [version]");
pw.println(" Prints true and returns exit status 0 when system has a FEATURE_NAME,");
pw.println(" otherwise prints false and returns exit status 1");
@@ -4299,7 +4329,7 @@
pw.println(" Options:");
pw.println(" -v: shows the location of the library in the device's filesystem");
pw.println("");
- pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+ pw.println(" list packages [-f] [-d] [-e] [-s] [-q] [-3] [-i] [-l] [-u] [-U] ");
pw.println(" [--show-versioncode] [--apex-only] [--factory-only]");
pw.println(" [--uid UID] [--user USER_ID] [FILTER]");
pw.println(" Prints all packages; optionally only those whose name contains");
@@ -4309,6 +4339,7 @@
pw.println(" -d: filter to only show disabled packages");
pw.println(" -e: filter to only show enabled packages");
pw.println(" -s: filter to only show system packages");
+ pw.println(" -q: filter to only show quarantined packages");
pw.println(" -3: filter to only show third party packages");
pw.println(" -i: see the installer for the packages");
pw.println(" -l: ignored (used for compatibility with older releases)");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index faf132e..2f68021 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -208,7 +208,6 @@
import com.android.internal.policy.PhoneWindow;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.ExtconStateObserver;
@@ -622,9 +621,16 @@
private boolean mAllowTheaterModeWakeFromLidSwitch;
private boolean mAllowTheaterModeWakeFromWakeGesture;
- // Whether to support long press from power button in non-interactive mode
+ // If true, the power button long press behavior will be invoked even if the default display is
+ // non-interactive. If false, the power button long press behavior will be skipped if the
+ // default display is non-interactive.
private boolean mSupportLongPressPowerWhenNonInteractive;
+ // If true, the power button short press behavior will be always invoked as long as the default
+ // display is on, even if the display is not interactive. If false, the power button short press
+ // behavior will be skipped if the default display is non-interactive.
+ private boolean mSupportShortPressPowerWhenDefaultDisplayOn;
+
// Whether to go to sleep entering theater mode from power button
private boolean mGoToSleepOnButtonPressTheaterMode;
@@ -1041,7 +1047,7 @@
}
}
- private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
+ private void powerPress(long eventTime, int count) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
// an auth attempt.
if (count == 1) {
@@ -1055,9 +1061,16 @@
final boolean interactive = mDefaultDisplayPolicy.isAwake();
- Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
- + " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
- + " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior);
+ Slog.d(
+ TAG,
+ "powerPress: eventTime="
+ + eventTime
+ + " interactive="
+ + interactive
+ + " count="
+ + count
+ + " mShortPressOnPowerBehavior="
+ + mShortPressOnPowerBehavior);
if (count == 2) {
powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
@@ -1065,12 +1078,7 @@
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
- } else if (count == 1 && interactive && !beganFromNonInteractive) {
- if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
- Slog.i(TAG, "Suppressing power key because the user is interacting with the "
- + "fingerprint sensor");
- return;
- }
+ } else if (count == 1 && shouldHandleShortPressPowerAction(interactive, eventTime)) {
switch (mShortPressOnPowerBehavior) {
case SHORT_PRESS_POWER_NOTHING:
break;
@@ -1118,6 +1126,44 @@
}
}
+ private boolean shouldHandleShortPressPowerAction(boolean interactive, long eventTime) {
+ if (mSupportShortPressPowerWhenDefaultDisplayOn) {
+ final boolean defaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
+ final boolean beganFromDefaultDisplayOn =
+ mSingleKeyGestureDetector.beganFromDefaultDisplayOn();
+ if (!defaultDisplayOn || !beganFromDefaultDisplayOn) {
+ Slog.v(
+ TAG,
+ "Ignoring short press of power button because the default display is not"
+ + " on. defaultDisplayOn="
+ + defaultDisplayOn
+ + ", beganFromDefaultDisplayOn="
+ + beganFromDefaultDisplayOn);
+ return false;
+ }
+ return true;
+ }
+ final boolean beganFromNonInteractive = mSingleKeyGestureDetector.beganFromNonInteractive();
+ if (!interactive || beganFromNonInteractive) {
+ Slog.v(
+ TAG,
+ "Ignoring short press of power button because the device is not interactive."
+ + " interactive="
+ + interactive
+ + ", beganFromNonInteractive="
+ + beganFromNonInteractive);
+ return false;
+ }
+ if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
+ Slog.i(
+ TAG,
+ "Suppressing power key because the user is interacting with the "
+ + "fingerprint sensor");
+ return false;
+ }
+ return true;
+ }
+
/**
* Attempt to dream from a power button press.
*
@@ -2231,6 +2277,11 @@
mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
+ mSupportShortPressPowerWhenDefaultDisplayOn =
+ mContext.getResources()
+ .getBoolean(
+ com.android.internal.R.bool
+ .config_supportShortPressPowerWhenDefaultDisplayOn);
mLongPressOnBackBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnBackBehavior);
@@ -2518,8 +2569,7 @@
@Override
void onPress(long downTime) {
- powerPress(downTime, 1 /*count*/,
- mSingleKeyGestureDetector.beganFromNonInteractive());
+ powerPress(downTime, 1 /*count*/);
}
@Override
@@ -2550,7 +2600,7 @@
@Override
void onMultiPress(long downTime, int count) {
- powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive());
+ powerPress(downTime, count);
}
}
@@ -4323,10 +4373,11 @@
// This could prevent some wrong state in multi-displays environment,
// the default display may turned off but interactive is true.
- final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
- final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
+ final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
+ final boolean isDefaultDisplayAwake = mDefaultDisplayPolicy.isAwake();
+ final boolean interactiveAndAwake = interactive && isDefaultDisplayAwake;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- handleKeyGesture(event, interactiveAndOn);
+ handleKeyGesture(event, interactiveAndAwake, isDefaultDisplayOn);
}
// Enable haptics if down and virtual key without multiple repetitions. If this is a hard
@@ -4479,7 +4530,7 @@
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
- interceptPowerKeyDown(event, interactiveAndOn);
+ interceptPowerKeyDown(event, interactiveAndAwake);
} else {
interceptPowerKeyUp(event, canceled);
}
@@ -4695,7 +4746,7 @@
return result;
}
- private void handleKeyGesture(KeyEvent event, boolean interactive) {
+ private void handleKeyGesture(KeyEvent event, boolean interactive, boolean defaultDisplayOn) {
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
@@ -4711,7 +4762,7 @@
}
}
- mSingleKeyGestureDetector.interceptKey(event, interactive);
+ mSingleKeyGestureDetector.interceptKey(event, interactive, defaultDisplayOn);
}
// The camera gesture will be detected by GestureLauncherService.
@@ -6167,6 +6218,9 @@
pw.print("mTriplePressOnPowerBehavior=");
pw.println(multiPressOnPowerBehaviorToString(mTriplePressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mSupportShortPressPowerWhenDefaultDisplayOn=");
+ pw.println(mSupportShortPressPowerWhenDefaultDisplayOn);
+ pw.print(prefix);
pw.print("mPowerVolUpBehavior=");
pw.println(powerVolumeUpBehaviorToString(mPowerVolUpBehavior));
pw.print(prefix);
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index b999bbb3..5fc0637 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -43,6 +43,7 @@
private int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
+ private boolean mBeganFromDefaultDisplayOn = false;
private final ArrayList<SingleKeyRule> mRules = new ArrayList();
private SingleKeyRule mActiveRule = null;
@@ -194,11 +195,12 @@
mRules.remove(rule);
}
- void interceptKey(KeyEvent event, boolean interactive) {
+ void interceptKey(KeyEvent event, boolean interactive, boolean defaultDisplayOn) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // Store the non interactive state when first down.
+ // Store the non interactive state and display on state when first down.
if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
mBeganFromNonInteractive = !interactive;
+ mBeganFromDefaultDisplayOn = defaultDisplayOn;
}
interceptKeyDown(event);
} else {
@@ -388,6 +390,10 @@
return mBeganFromNonInteractive;
}
+ boolean beganFromDefaultDisplayOn() {
+ return mBeganFromDefaultDisplayOn;
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "SingleKey rules:");
for (SingleKeyRule rule : mRules) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index cb09aef..5b36cd6 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3055,10 +3055,10 @@
TvInputInfo tvInputInfo = tvInputState.info;
int inputState = tvInputState.state;
int inputType = tvInputInfo.getType();
- // For non-CEC input, the value of vendorId is 0.
- int vendorId = 0;
- // For non-HDMI input, the value of hdmiPort is 0.
- int hdmiPort = 0;
+ // For non-CEC input, the value of vendorId is 0xFFFFFF (16777215 in decimal).
+ int vendorId = 16777215;
+ // For non-HDMI input, the value of hdmiPort is -1.
+ int hdmiPort = -1;
String tifSessionId = sessionState.sessionId;
if (tvInputInfo.getType() == TvInputInfo.TYPE_HDMI) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6c848d1..fb62412 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2606,9 +2606,6 @@
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- // When a task is locked, dismiss the root pinned task if it exists
- mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
-
getLockTaskController().startLockTaskMode(task, isSystemCaller, callingUid);
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 0c98fb5..0f9998c 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
@@ -669,6 +670,9 @@
}
}
+ // When a task is locked, dismiss the root pinned task if it exists
+ mSupervisor.mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
+
// System can only initiate screen pinning, not full lock task mode
ProtoLog.w(WM_DEBUG_LOCKTASK, "%s", isSystemCaller ? "Locking pinned" : "Locking fully");
setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6432ff0..6ede345 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1124,14 +1124,15 @@
+ "track #%d", transition.getSyncId(), track);
}
}
- if (sync) {
+ transition.mAnimationTrack = track;
+ info.setTrack(track);
+ mTrackCount = Math.max(mTrackCount, track + 1);
+ if (sync && mTrackCount > 1) {
+ // If there are >1 tracks, mark as sync so that all tracks finish.
info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
transition.getSyncId());
}
- transition.mAnimationTrack = track;
- info.setTrack(track);
- mTrackCount = Math.max(mTrackCount, track + 1);
}
void updateAnimatingState(SurfaceControl.Transaction t) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5a45fe1..a172d99 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -30,7 +30,6 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -1446,9 +1445,7 @@
}
final boolean dragResizingChanged = !mDragResizingChangeReported && isDragResizeChanged();
-
- final boolean attachedFrameChanged = LOCAL_LAYOUT
- && mLayoutAttached && getParentWindow().frameChanged();
+ final boolean attachedFrameChanged = mLayoutAttached && getParentWindow().frameChanged();
if (DEBUG) {
Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index 74dc853..7552800 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -56,7 +56,7 @@
import android.os.RemoteException;
import android.os.StatFs;
import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.Postsubmit;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -93,7 +93,7 @@
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
-@Presubmit
+@Postsubmit
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
new file mode 100644
index 0000000..4fd8f26
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link DisplayDevice} class.
+ *
+ * Build/Install/Run:
+ * atest DisplayServicesTests:DisplayDeviceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DisplayDeviceTest {
+ private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
+ private static final int WIDTH = 500;
+ private static final int HEIGHT = 900;
+ private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+
+ @Mock
+ private SurfaceControl.Transaction mMockTransaction;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDisplayDeviceInfo.width = WIDTH;
+ mDisplayDeviceInfo.height = HEIGHT;
+ mDisplayDeviceInfo.rotation = ROTATION_0;
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation0() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_0, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation180() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_180, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation270() {
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_270, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+ }
+
+ private static class FakeDisplayDevice extends DisplayDevice {
+ private final DisplayDeviceInfo mDisplayDeviceInfo;
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo) {
+ super(null, null, "", InstrumentationRegistry.getInstrumentation().getContext());
+ mDisplayDeviceInfo = displayDeviceInfo;
+ }
+
+ @Override
+ public boolean hasStableUniqueId() {
+ return false;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return mDisplayDeviceInfo;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index de27d77..e672928 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -102,7 +102,7 @@
).when(() -> Settings.Global.getString(any(), anyString()));
mTestMapper = new SettingsToPropertiesMapper(
- mMockContentResolver, TEST_MAPPING, new String[] {});
+ mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index bcbbcd4..908afc8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -61,6 +61,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -1729,7 +1730,9 @@
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, mVdms, new Binder(), ownerUid, virtualDeviceId,
+ mAssociationInfo, mVdms, new Binder(),
+ new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"),
+ virtualDeviceId,
mInputController, mCameraAccessController,
mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e1f3c2b..57aa0b9 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7753,7 +7753,8 @@
public void testOnNotificationActionClick() {
final int actionIndex = 2;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", null).build();
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
final boolean generatedByAssistant = false;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -7777,7 +7778,8 @@
public void testOnAssistantNotificationActionClick() {
final int actionIndex = 1;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", null).build();
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 3bb86a7..b9492e9 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -170,10 +170,15 @@
}
private void pressKey(int keyCode, long pressTime, boolean interactive) {
+ pressKey(keyCode, pressTime, interactive, false /* defaultDisplayOn */);
+ }
+
+ private void pressKey(
+ int keyCode, long pressTime, boolean interactive, boolean defaultDisplayOn) {
long eventTime = SystemClock.uptimeMillis();
final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN,
keyCode, 0 /* repeat */, 0 /* metaState */);
- mDetector.interceptKey(keyDown, interactive);
+ mDetector.interceptKey(keyDown, interactive, defaultDisplayOn);
// keep press down.
try {
@@ -186,7 +191,7 @@
final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP,
keyCode, 0 /* repeat */, 0 /* metaState */);
- mDetector.interceptKey(keyUp, interactive);
+ mDetector.interceptKey(keyUp, interactive, defaultDisplayOn);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 16c38ac..08438c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -354,25 +354,37 @@
}
});
- final LandscapeActivity activity =
- (LandscapeActivity) startTestActivity(LandscapeActivity.class);
+ final boolean isIgnoringOrientationRequest =
+ CommonUtils.getIgnoreOrientationRequest(Display.DEFAULT_DISPLAY);
+ if (isIgnoringOrientationRequest) {
+ CommonUtils.setIgnoreOrientationRequest(Display.DEFAULT_DISPLAY, false);
+ }
- int[] taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
- candidate -> candidate[0] == activity.getTaskId());
- assertNotNull(taskIdAndOrientation);
- assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, taskIdAndOrientation[1]);
+ try {
+ final LandscapeActivity activity =
+ (LandscapeActivity) startTestActivity(LandscapeActivity.class);
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
- taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
- candidate -> candidate[0] == activity.getTaskId());
- assertNotNull(taskIdAndOrientation);
- assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, taskIdAndOrientation[1]);
+ int[] taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
+ candidate -> candidate[0] == activity.getTaskId());
+ assertNotNull(taskIdAndOrientation);
+ assertEquals(
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, taskIdAndOrientation[1]);
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
- candidate -> candidate[0] == activity.getTaskId());
- assertNotNull(taskIdAndOrientation);
- assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, taskIdAndOrientation[1]);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
+ taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
+ candidate -> candidate[0] == activity.getTaskId());
+ assertNotNull(taskIdAndOrientation);
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, taskIdAndOrientation[1]);
+
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ taskIdAndOrientation = waitForResult(taskIdAndOrientationQueue,
+ candidate -> candidate[0] == activity.getTaskId());
+ assertNotNull(taskIdAndOrientation);
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, taskIdAndOrientation[1]);
+ } finally {
+ CommonUtils.setIgnoreOrientationRequest(
+ Display.DEFAULT_DISPLAY, isIgnoringOrientationRequest);
+ }
}
/**
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 07cdfaf..9b6d4e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -39,6 +39,7 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_SYNC;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
@@ -2390,6 +2391,37 @@
assertFalse(controller.isCollecting());
}
+ @Test
+ public void testNoSyncFlagIfOneTrack() {
+ final TransitionController controller = mAtm.getTransitionController();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ mSyncEngine = createTestBLASTSyncEngine();
+ controller.setSyncEngine(mSyncEngine);
+
+ final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
+
+ controller.startCollectOrQueue(transitA, (deferred) -> {});
+ controller.startCollectOrQueue(transitB, (deferred) -> {});
+ controller.startCollectOrQueue(transitC, (deferred) -> {});
+
+ // Verify that, as-long as there is <= 1 track, we won't get a SYNC flag
+ transitA.start();
+ transitA.setAllReady();
+ mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ transitB.start();
+ transitB.setAllReady();
+ mSyncEngine.tryFinishForTest(transitB.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ transitC.start();
+ transitC.setAllReady();
+ mSyncEngine.tryFinishForTest(transitC.getSyncId());
+ assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
index ed23296..bfbba5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
@@ -21,9 +21,12 @@
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.UiAutomation;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import android.view.IWindowManager;
import android.view.KeyEvent;
+import android.view.WindowManagerGlobal;
import androidx.test.uiautomator.UiDevice;
@@ -48,6 +51,37 @@
}
}
+ public static boolean getIgnoreOrientationRequest(int displayId) {
+ final UiDevice uiDevice = UiDevice.getInstance(getInstrumentation());
+ final String result;
+ try {
+ result = uiDevice.executeShellCommand("cmd window get-ignore-orientation-request -d "
+ + displayId);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ final String[] tokens = result.split(" ");
+ if (tokens.length != 4) {
+ throw new RuntimeException("Expecting a result with 4 tokens, but got " + result);
+ }
+
+ // The output looks like "ignoreOrientationRequest true for displayId=0"
+ return Boolean.parseBoolean(tokens[1]);
+ }
+
+ public static void setIgnoreOrientationRequest(
+ int displayId, boolean ignoreOrientationRequest) {
+ runWithShellPermissionIdentity(() -> {
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ wm.setIgnoreOrientationRequest(displayId, ignoreOrientationRequest);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
/** Dismisses the Keyguard if it is locked. */
public static void dismissKeyguard() {
final KeyguardManager keyguardManager = getInstrumentation().getContext().getSystemService(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 58da4b43..3d78a1d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -54,6 +54,7 @@
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordDetector;
+import android.service.voice.IDetectorSessionStorageService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -69,6 +70,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.server.LocalServices;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -157,6 +159,8 @@
@NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
@GuardedBy("mLock")
@Nullable private IBinder mAudioFlinger;
+
+ @Nullable private IHotwordRecognitionStatusCallback mHotwordRecognitionCallback;
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
@@ -694,6 +698,7 @@
updateContentCaptureManager(connection);
updateSpeechService(connection);
updateServiceIdentity(connection);
+ updateStorageService(connection);
return connection;
}
}
@@ -910,6 +915,7 @@
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
}
+ mHotwordRecognitionCallback = callback;
mDetectorSessions.put(detectorType, session);
session.initialize(options, sharedMemory);
}
@@ -1035,6 +1041,23 @@
}));
}
+ private void updateStorageService(ServiceConnection connection) {
+ connection.run(service -> {
+ service.registerRemoteStorageService(new IDetectorSessionStorageService.Stub() {
+ @Override
+ public void openFile(String filename, AndroidFuture future)
+ throws RemoteException {
+ Slog.v(TAG, "BinderCallback#onFileOpen");
+ try {
+ mHotwordRecognitionCallback.onOpenFile(filename, future);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ });
+ });
+ }
+
private void addServiceUidForAudioPolicy(int uid) {
mScheduledExecutorService.execute(() -> {
AudioManagerInternal audioManager =
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index dcaf858..b57b7c7 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -4,7 +4,6 @@
tgunn@google.com
xiaotonj@google.com
rgreenwalt@google.com
-chinmayd@google.com
grantmenke@google.com
pmadapurmath@google.com
tjstuart@google.com
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
new file mode 100644
index 0000000..87231c8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.datatypes.Rect
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.utils.*
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/***
+ * Test entering System SplitScreen with Activity Embedding Split and another app.
+ *
+ * Setup: Launch A|B in split and secondaryApp, return to home.
+ * Transitions: Let AE Split A|B enter splitscreen with secondaryApp. Resulting in A|B|secondaryApp.
+ *
+ * To run this test: `atest FlickerTestsOther:EnterSystemSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSystemSplitTest(flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ private val secondaryApp = SplitScreenUtils.getPrimary(instrumentation)
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?:
+ error("Display not found")
+ }
+ transitions {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, testApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitLayerBecomesVisible() {
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ testApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ }
+
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitWindowBecomesVisible() = flicker.appWindowIsVisibleAtEnd(testApp)
+
+ @Presubmit
+ @Test
+ fun secondaryLayerBecomesVisible() {
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /**
+ * After the transition there should be both ActivityEmbedding activities,
+ * SplitScreenPrimaryActivity and the system split divider on screen.
+ * Verify the layers are in expected sizes.
+ */
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitSurfaceAreEven() {
+ flicker.assertLayersEnd {
+ val leftAELayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightAELayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ val secondaryAppLayerRegion =
+ visibleRegion(
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
+ val systemDivider = visibleRegion(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ leftAELayerRegion
+ .plus(rightAELayerRegion.region)
+ .plus(secondaryAppLayerRegion.region)
+ .plus(systemDivider.region)
+ .coversExactly(startDisplayBounds)
+ check { "ActivityEmbeddingSplitHeight" }
+ .that(leftAELayerRegion.region.height)
+ .isEqual(rightAELayerRegion.region.height)
+ check { "SystemSplitHeight" }
+ .that(rightAELayerRegion.region.height)
+ .isEqual(secondaryAppLayerRegion.region.height)
+ // TODO(b/292283182): Remove this special case handling.
+ check { "ActivityEmbeddingSplitWidth" }
+ .that(Math.abs(
+ leftAELayerRegion.region.width - rightAELayerRegion.region.width))
+ .isLower(2)
+ check { "SystemSplitWidth" }
+ .that(Math.abs(secondaryAppLayerRegion.region.width -
+ 2 * rightAELayerRegion.region.width))
+ .isLower(2)
+ }
+ }
+
+ /**
+ * Verify the windows are in expected sizes.
+ */
+ @Presubmit
+ @Test
+ fun activityEmbeddingSplitWindowsAreEven() {
+ flicker.assertWmEnd {
+ val leftAEWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightAEWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ // There's no window for the divider bar.
+ val secondaryAppLayerRegion =
+ visibleRegion(
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
+ check { "ActivityEmbeddingSplitHeight" }
+ .that(leftAEWindowRegion.region.height)
+ .isEqual(rightAEWindowRegion.region.height)
+ check { "SystemSplitHeight" }
+ .that(rightAEWindowRegion.region.height)
+ .isEqual(secondaryAppLayerRegion.region.height)
+ check { "ActivityEmbeddingSplitWidth" }
+ .that(Math.abs(
+ leftAEWindowRegion.region.width - rightAEWindowRegion.region.width))
+ .isLower(2)
+ check { "SystemSplitWidth" }
+ .that(Math.abs(secondaryAppLayerRegion.region.width -
+ 2 * rightAEWindowRegion.region.width))
+ .isLower(2)
+ }
+ }
+
+ @Ignore("Not applicable to this CUJ.")
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
new file mode 100644
index 0000000..1b98887
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/FocusEventDebugViewTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FocusEventDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FocusEventDebugViewTest {
+
+ private FocusEventDebugView mFocusEventDebugView;
+ private FocusEventDebugView.RotaryInputValueView mRotaryInputValueView;
+ private FocusEventDebugView.RotaryInputGraphView mRotaryInputGraphView;
+ private float mScaledVerticalScrollFactor;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ mScaledVerticalScrollFactor =
+ ViewConfiguration.get(context).getScaledVerticalScrollFactor();
+ InputManagerService mockService = mock(InputManagerService.class);
+ when(mockService.monitorInput(anyString(), anyInt()))
+ .thenReturn(InputChannel.openInputChannelPair("FocusEventDebugViewTest")[1]);
+
+ mRotaryInputValueView = new FocusEventDebugView.RotaryInputValueView(context);
+ mRotaryInputGraphView = new FocusEventDebugView.RotaryInputGraphView(context);
+ mFocusEventDebugView = new FocusEventDebugView(context, mockService,
+ () -> mRotaryInputValueView, () -> mRotaryInputGraphView);
+ }
+
+ @Test
+ public void startsRotaryInputValueViewWithDefaultValue() {
+ assertEquals("+0.0", mRotaryInputValueView.getText());
+ }
+
+ @Test
+ public void startsRotaryInputGraphViewWithDefaultFrameCenter() {
+ assertEquals(0, mRotaryInputGraphView.getFrameCenterPosition(), 0.01);
+ }
+
+ @Test
+ public void handleRotaryInput_updatesRotaryInputValueViewWithScrollValue() {
+ mFocusEventDebugView.handleUpdateShowRotaryInput(true);
+
+ mFocusEventDebugView.handleRotaryInput(createRotaryMotionEvent(0.5f));
+
+ assertEquals(String.format("+%.1f", 0.5f * mScaledVerticalScrollFactor),
+ mRotaryInputValueView.getText());
+ }
+
+ @Test
+ public void handleRotaryInput_translatesRotaryInputGraphViewWithHighScrollValue() {
+ mFocusEventDebugView.handleUpdateShowRotaryInput(true);
+
+ mFocusEventDebugView.handleRotaryInput(createRotaryMotionEvent(1000f));
+
+ assertTrue(mRotaryInputGraphView.getFrameCenterPosition() > 0);
+ }
+
+ @Test
+ public void updateActivityStatus_setsAndRemovesColorFilter() {
+ // It should not be active initially.
+ assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+ mRotaryInputValueView.updateActivityStatus(true);
+ // It should be active after rotary input.
+ assertNotNull(mRotaryInputValueView.getBackground().getColorFilter());
+
+ mRotaryInputValueView.updateActivityStatus(false);
+ // It should not be active after waiting for mUpdateActivityStatusCallback.
+ assertNull(mRotaryInputValueView.getBackground().getColorFilter());
+ }
+
+ private MotionEvent createRotaryMotionEvent(float scrollAxisValue) {
+ PointerCoords pointerCoords = new PointerCoords();
+ pointerCoords.setAxisValue(MotionEvent.AXIS_SCROLL, scrollAxisValue);
+ PointerProperties pointerProperties = new PointerProperties();
+
+ return MotionEvent.obtain(
+ /* downTime */ 0,
+ /* eventTime */ 0,
+ /* action */ MotionEvent.ACTION_SCROLL,
+ /* pointerCount */ 1,
+ /* pointerProperties */ new PointerProperties[] {pointerProperties},
+ /* pointerCoords */ new PointerCoords[] {pointerCoords},
+ /* metaState */ 0,
+ /* buttonState */ 0,
+ /* xPrecision */ 0,
+ /* yPrecision */ 0,
+ /* deviceId */ 0,
+ /* edgeFlags */ 0,
+ /* source */ InputDevice.SOURCE_ROTARY_ENCODER,
+ /* flags */ 0
+ );
+ }
+}
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java
index ba12acb..2b605c5 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java
@@ -18,6 +18,7 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -41,11 +42,11 @@
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
+import androidx.appcompat.app.AppCompatActivity;
+
import java.util.ArrayList;
import java.util.List;
-import androidx.appcompat.app.AppCompatActivity;
-
public class ChatActivity extends AppCompatActivity {
private View mRoot;
@@ -148,7 +149,7 @@
inset = min(inset, shown);
mAnimationController.setInsetsAndAlpha(
Insets.of(0, 0, 0, inset),
- 1f, (inset - start) / (float)(end - start));
+ 1f, start == end ? 1f : (inset - start) / (float) (end - start));
}
});