Merge "Update device-entry docs + add glossary" into tm-dev
diff --git a/Android.bp b/Android.bp
index cd110de..8ac7de9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -324,6 +324,7 @@
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-build",
"modules-utils-preconditions",
"modules-utils-synchronous-result-receiver",
"modules-utils-os",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index f26e051..c6ba1ea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -307,7 +307,7 @@
private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
/**
- * Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
+ * Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed
* realtime timebase).
*/
private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
@@ -815,6 +815,19 @@
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
}
+ private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket,
+ long nowElapsed) {
+ if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) {
+ // Don't let RESTRICTED apps get free quota from the temp allowlist.
+ // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows
+ // them to start FGS
+ return false;
+ }
+ final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid);
+ return mTempAllowlistCache.get(sourceUid)
+ || nowElapsed < tempAllowlistGracePeriodEndElapsed;
+ }
+
/** @return true if the job is within expedited job quota. */
@GuardedBy("mLock")
public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
@@ -833,11 +846,8 @@
}
final long nowElapsed = sElapsedRealtimeClock.millis();
- final long tempAllowlistGracePeriodEndElapsed =
- mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
- final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
- || nowElapsed < tempAllowlistGracePeriodEndElapsed;
- if (hasTempAllowlistExemption) {
+ if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(),
+ jobStatus.getEffectiveStandbyBucket(), nowElapsed)) {
return true;
}
@@ -2127,10 +2137,8 @@
final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
mPkg.userId, nowElapsed);
- final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
final boolean hasTempAllowlistExemption = !mRegularJobTimer
- && (mTempAllowlistCache.get(mUid)
- || nowElapsed < tempAllowlistGracePeriodEndElapsed);
+ && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed);
final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
final boolean hasTopAppExemption = !mRegularJobTimer
&& (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
index 6a4a4be..7d9260a 100644
--- a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
+++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
@@ -28,43 +28,13 @@
public final class LockSettingsCmd extends BaseCommand {
- private static final String USAGE =
- "usage: locksettings set-pattern [--old OLD_CREDENTIAL] NEW_PATTERN\n" +
- " locksettings set-pin [--old OLD_CREDENTIAL] NEW_PIN\n" +
- " locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" +
- " locksettings clear [--old OLD_CREDENTIAL]\n" +
- " locksettings verify [--old OLD_CREDENTIAL]\n" +
- " locksettings set-disabled DISABLED\n" +
- " locksettings get-disabled\n" +
- "\n" +
- "flags: \n" +
- " --user USER_ID: specify the user, default value is current user\n" +
- "\n" +
- "locksettings set-pattern: sets a pattern\n" +
- " A pattern is specified by a non-separated list of numbers that index the cell\n" +
- " on the pattern in a 1-based manner in left to right and top to bottom order,\n" +
- " i.e. the top-left cell is indexed with 1, whereas the bottom-right cell\n" +
- " is indexed with 9. Example: 1234\n" +
- "\n" +
- "locksettings set-pin: sets a PIN\n" +
- "\n" +
- "locksettings set-password: sets a password\n" +
- "\n" +
- "locksettings clear: clears the unlock credential\n" +
- "\n" +
- "locksettings verify: verifies the credential and unlocks the user\n" +
- "\n" +
- "locksettings set-disabled: sets whether the lock screen should be disabled\n" +
- "\n" +
- "locksettings get-disabled: retrieves whether the lock screen is disabled\n";
-
public static void main(String[] args) {
(new LockSettingsCmd()).run(args);
}
@Override
public void onShowUsage(PrintStream out) {
- out.println(USAGE);
+ main(new String[] { "help" });
}
@Override
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index c022bf3..9c391ab 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -562,14 +562,15 @@
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
/**
- * Checks if there is an unfinished instrumentation that targets the given uid.
+ * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+ * targets the given uid.
*
* @param uid The uid to be checked for
*
- * @return True, if there is an instrumentation whose target application uid matches the given
- * uid, false otherwise
+ * @return the uid of the instrumentation source, if there is an instrumentation whose target
+ * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
*/
- public abstract boolean isUidCurrentlyInstrumented(int uid);
+ public abstract int getInstrumentationSourceUid(int uid);
/** Is this a device owner app? */
public abstract boolean isDeviceOwner(int uid);
@@ -791,10 +792,11 @@
*
* @param packageName The package name of the process.
* @param uid The UID of the process.
- * @param foregroundId The current foreground service notification ID, a negative value
- * means this notification is being removed.
+ * @param foregroundId The current foreground service notification ID.
+ * @param canceling The given notification is being canceled.
*/
- void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
+ void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId,
+ boolean canceling);
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b843a9..852dd97 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -536,6 +536,9 @@
// A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
// used without security checks
public IBinder shareableActivityToken;
+ // The token of the initial TaskFragment that embedded this activity. Do not rely on it
+ // after creation because the activity could be reparented.
+ @Nullable public IBinder mInitialTaskFragmentToken;
int ident;
@UnsupportedAppUsage
Intent intent;
@@ -618,7 +621,8 @@
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
- IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
+ IBinder initialTaskFragmentToken) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -639,6 +643,7 @@
compatInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
+ mInitialTaskFragmentToken = initialTaskFragmentToken;
init();
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 638b30e..e9df50f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,10 +1058,11 @@
}
/**
- * Sends the key events corresponding to the text to the app being
- * instrumented.
- *
- * @param text The text to be sent.
+ * Sends the key events that result in the given text being typed into the currently focused
+ * window, and waits for it to be processed.
+ *
+ * @param text The text to be sent.
+ * @see #sendKeySync(KeyEvent)
*/
public void sendStringSync(String text) {
if (text == null) {
@@ -1084,11 +1085,12 @@
}
/**
- * Send a key event to the currently focused window/view and wait for it to
- * be processed. Finished at some point after the recipient has returned
- * from its event processing, though it may <em>not</em> have completely
- * finished reacting from the event -- for example, if it needs to update
- * its display as a result, it may still be in the process of doing that.
+ * Sends a key event to the currently focused window, and waits for it to be processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event The event to send to the current focus.
*/
@@ -1116,34 +1118,42 @@
}
/**
- * Sends an up and down key event sync to the currently focused window.
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
*
- * @param key The integer keycode for the event.
+ * @param keyCode The key code for the events to send.
+ * @see #sendKeySync(KeyEvent)
*/
- public void sendKeyDownUpSync(int key) {
- sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
- sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
- }
-
- /**
- * Higher-level method for sending both the down and up key events for a
- * particular character key code. Equivalent to creating both KeyEvent
- * objects by hand and calling {@link #sendKeySync}. The event appears
- * as if it came from keyboard 0, the built in one.
- *
- * @param keyCode The key code of the character to send.
- */
- public void sendCharacterSync(int keyCode) {
+ public void sendKeyDownUpSync(int keyCode) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
-
+
/**
- * Dispatch a pointer event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
+ * <p>
+ * Equivalent to {@link #sendKeyDownUpSync(int)}.
+ *
+ * @param keyCode The key code of the character to send.
+ * @see #sendKeySync(KeyEvent)
+ */
+ public void sendCharacterSync(int keyCode) {
+ sendKeyDownUpSync(keyCode);
+ }
+
+ /**
+ * Dispatches a pointer event into a window owned by the instrumented application, and waits for
+ * it to be processed.
+ * <p>
+ * If the motion event being injected is targeted at a window that is not owned by the
+ * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
+ * injecting events into all windows.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event A motion event describing the pointer action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
@@ -1155,10 +1165,10 @@
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- syncInputTransactionsAndInjectEvent(event);
+ syncInputTransactionsAndInjectEventIntoSelf(event);
}
- private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
+ private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1169,8 +1179,9 @@
.syncInputTransactions(true /*waitForAnimations*/);
}
+ // Direct the injected event into windows owned by the instrumentation target.
InputManager.getInstance().injectInputEvent(
- event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
+ event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());
if (syncAfter) {
WindowManagerGlobal.getWindowManagerService()
@@ -1182,19 +1193,26 @@
}
/**
- * Dispatch a trackball event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
- *
+ * Dispatches a trackball event into the currently focused window, and waits for it to be
+ * processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
+ *
* @param event A motion event describing the trackball action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendTrackballEventSync(MotionEvent event) {
validateNotAppThread();
- if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
+ if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ throw new IllegalArgumentException(
+ "Cannot inject pointer events from sendTrackballEventSync()."
+ + " Use sendPointerSync() to inject pointer events.");
+ }
+ if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 88ffdec..d375a9e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1682,7 +1682,7 @@
public @interface ProvisioningConfiguration {}
/**
- * A String extra holding the provisioning trigger. It could be one of
+ * An int extra holding the provisioning trigger. It could be one of
* {@link #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT}, {@link #PROVISIONING_TRIGGER_QR_CODE},
* {@link #PROVISIONING_TRIGGER_MANAGED_ACCOUNT} or {@link
* #PROVISIONING_TRIGGER_UNSPECIFIED}.
@@ -3298,9 +3298,9 @@
* Activity action: Starts the device policy management role holder updater.
*
* <p>The activity must handle the device policy management role holder update and set the
- * intent result to either {@link Activity#RESULT_OK} if the update was successful, {@link
- * #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a
- * problem that may be solved by relaunching it again, {@link
+ * intent result to either {@link Activity#RESULT_OK} if the update was successful or not
+ * necessary, {@link #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if
+ * it encounters a problem that may be solved by relaunching it again, {@link
* #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_PROVISIONING_DISABLED} if role holder
* provisioning is disabled, or {@link
* #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index d7e0951..076dbef 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -72,6 +72,7 @@
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
private boolean mLaunchedFromBubble;
+ private IBinder mTaskFragmentToken;
/**
* It is only non-null if the process is the first time to launch activity. It is only an
* optimization for quick look up of the interface so the field is ignored for comparison.
@@ -95,7 +96,8 @@
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble);
+ client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+ mTaskFragmentToken);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -119,7 +121,7 @@
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
IActivityClientController activityClientController, IBinder shareableActivityToken,
- boolean launchedFromBubble) {
+ boolean launchedFromBubble, IBinder taskFragmentToken) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
instance = new LaunchActivityItem();
@@ -128,7 +130,7 @@
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
activityClientController, shareableActivityToken,
- launchedFromBubble);
+ launchedFromBubble, taskFragmentToken);
return instance;
}
@@ -136,7 +138,7 @@
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, false);
+ null, false, null, null, null, null, false, null);
ObjectPool.recycle(this);
}
@@ -166,6 +168,7 @@
dest.writeStrongInterface(mActivityClientController);
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
+ dest.writeStrongBinder(mTaskFragmentToken);
}
/** Read from Parcel. */
@@ -184,7 +187,8 @@
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
in.readStrongBinder(),
- in.readBoolean());
+ in.readBoolean(),
+ in.readStrongBinder());
}
public static final @NonNull Creator<LaunchActivityItem> CREATOR =
@@ -222,7 +226,8 @@
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
- && Objects.equals(mShareableActivityToken, other.mShareableActivityToken);
+ && Objects.equals(mShareableActivityToken, other.mShareableActivityToken)
+ && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken);
}
@Override
@@ -244,6 +249,7 @@
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
result = 31 * result + Objects.hashCode(mShareableActivityToken);
+ result = 31 * result + Objects.hashCode(mTaskFragmentToken);
return result;
}
@@ -291,7 +297,7 @@
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
IBinder assistToken, IActivityClientController activityClientController,
- IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder shareableActivityToken, boolean launchedFromBubble, IBinder taskFragmentToken) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -312,5 +318,6 @@
instance.mActivityClientController = activityClientController;
instance.mShareableActivityToken = shareableActivityToken;
instance.mLaunchedFromBubble = launchedFromBubble;
+ instance.mTaskFragmentToken = taskFragmentToken;
}
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5680bcd..cb55e30 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -568,7 +568,8 @@
}
ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
- targetVer, targetCode, SDK_CODENAMES, input);
+ targetVer, targetCode, SDK_CODENAMES, input,
+ /* allowUnknownCodenames= */ false);
if (targetResult.isError()) {
return input.error(targetResult);
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index a65b681..6d74b81 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -36,6 +36,7 @@
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
@@ -334,8 +335,9 @@
* If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code
* targetVers} will be returned unmodified.
* <p>
- * Otherwise, the behavior varies based on whether the current platform is a pre-release
- * version, e.g. the {@code platformSdkCodenames} array has length > 0:
+ * When {@code allowUnknownCodenames} is false, the behavior varies based on whether the
+ * current platform is a pre-release version, e.g. the {@code platformSdkCodenames} array has
+ * length > 0:
* <ul>
* <li>If this is a pre-release platform and the value specified by
* {@code targetCode} is contained within the array of allowed pre-release
@@ -343,22 +345,32 @@
* <li>If this is a released platform, this method will return -1 to
* indicate that the package is not compatible with this platform.
* </ul>
+ * <p>
+ * When {@code allowUnknownCodenames} is true, any codename that is not known (presumed to be
+ * a codename announced after the build of the current device) is allowed and this method will
+ * return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
*
- * @param targetVers targetSdkVersion number, if specified in the application
- * manifest, or 0 otherwise
- * @param targetCode targetSdkVersion code, if specified in the application manifest,
- * or {@code null} otherwise
- * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+ * @param targetVers targetSdkVersion number, if specified in the application
+ * manifest, or 0 otherwise
+ * @param targetCode targetSdkVersion code, if specified in the application manifest,
+ * or {@code null} otherwise
+ * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+ * @param allowUnknownCodenames allow unknown codenames, if true this method will accept unknown
+ * (presumed to be future) codenames
* @return the targetSdkVersion to use at runtime if successful
*/
public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
- @NonNull ParseInput input) {
+ @NonNull ParseInput input, boolean allowUnknownCodenames) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return input.success(targetVers);
}
+ if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index e1ffd4a..57e84bd 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,10 +57,11 @@
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
- // Injects an input event into the system. To inject into windows owned by other
- // applications, the caller must have the INJECT_EVENTS permission.
+ // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
+ // The caller can target windows owned by a certain UID by providing a valid UID, or by
+ // providing {@link android.os.Process#INVALID_UID} to target all windows.
@UnsupportedAppUsage
- boolean injectInputEvent(in InputEvent ev, int mode);
+ boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index c38a847..0bcabdd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,6 +45,7 @@
import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,58 @@
}
}
+ /**
+ * Injects an input event into the event system, targeting windows owned by the provided uid.
+ *
+ * If a valid targetUid is provided, the system will only consider injecting the input event
+ * into windows owned by the provided uid. If the input event is targeted at a window that is
+ * not owned by the provided uid, input injection will fail and a RemoteException will be
+ * thrown.
+ *
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * <p>
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+ * </p><p>
+ * Make sure you correctly set the event time and input source of the event
+ * before calling this method.
+ * </p>
+ *
+ * @param event The event to inject.
+ * @param mode The synchronization mode. One of:
+ * {@link android.os.InputEventInjectionSync.NONE},
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+ * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
+ * windows.
+ * @return True if input event injection succeeded.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+ public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mode != InputEventInjectionSync.NONE
+ && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+ && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
+ throw new IllegalArgumentException("mode is invalid");
+ }
+
+ try {
+ return mIm.injectInputEvent(event, mode, targetUid);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
/**
* Injects an input event into the event system on behalf of an application.
* The synchronization mode determines whether the method blocks while waiting for
* input injection to proceed.
* <p>
- * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
- * windows that are owned by other applications.
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
@@ -1129,22 +1174,10 @@
*
* @hide
*/
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
@UnsupportedAppUsage
public boolean injectInputEvent(InputEvent event, int mode) {
- if (event == null) {
- throw new IllegalArgumentException("event must not be null");
- }
- if (mode != InputEventInjectionSync.NONE
- && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
- && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
- throw new IllegalArgumentException("mode is invalid");
- }
-
- try {
- return mIm.injectInputEvent(event, mode);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return injectInputEvent(event, mode, Process.INVALID_UID);
}
/**
@@ -1421,8 +1454,11 @@
}
mInputDevices = new SparseArray<InputDevice>();
- for (int i = 0; i < ids.length; i++) {
- mInputDevices.put(ids[i], null);
+ // TODO(b/223905476): remove when the rootcause is fixed.
+ if (ids != null) {
+ for (int i = 0; i < ids.length; i++) {
+ mInputDevices.put(ids[i], null);
+ }
}
}
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c4cb319..a64e63e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,6 +300,10 @@
* When it is set by any of these owners, it prevents all users from using
* Wi-Fi tethering. Other forms of tethering are not affected.
*
+ * This user restriction disables only Wi-Fi tethering.
+ * Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
+ * When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -735,7 +739,7 @@
public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
/**
- * Specifies if a user is disallowed from configuring Tethering and portable hotspots
+ * Specifies if a user is disallowed from using and configuring Tethering and portable hotspots
* via Settings.
*
* <p>This restriction can only be set by a device owner, a profile owner on the primary
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 001707d..d4f8a3b 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1038,14 +1038,14 @@
}
mFinished = true;
+ mOverlayConnection.unbind(this);
+
if (mDreamToken == null) {
Slog.w(mTag, "Finish was called before the dream was attached.");
stopSelf();
return;
}
- mOverlayConnection.unbind(this);
-
try {
// finishSelf will unbind the dream controller from the dream service. This will
// trigger DreamService.this.onDestroy and DreamService.this will die.
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 01a037a..4e7b3a5 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -193,6 +193,16 @@
}
final Network network = connectivityManager.getActiveNetwork();
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+
+ // This connectivity check is to avoid performing a DNS lookup for the time server on a
+ // unconnected network. There are races to obtain time in Android when connectivity
+ // changes, which means that forceRefresh() can be called by various components before
+ // the network is actually available. This led in the past to DNS lookup failures being
+ // cached (~2 seconds) thereby preventing the device successfully making an NTP request
+ // when connectivity had actually been established.
+ // A side effect of check is that tests that run a fake NTP server on the device itself
+ // will only be able to use it if the active network is connected, even though loopback
+ // addresses are actually reachable.
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
return false;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5f0098c..0c4d9bf 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1584,10 +1584,10 @@
return false;
}
final Configuration config = mResources.getConfiguration();
- // TODO(b/179308296) Temporarily - never report max bounds to only Launcher if the feature
- // is disabled.
+ // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking
+ // if the caller is the recents component.
return config != null && !config.windowConfiguration.getMaxBounds().isEmpty()
- && (mDisplayInfo.shouldConstrainMetricsForLauncher || !isRecentsComponent());
+ && !isRecentsComponent();
}
/**
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 6917d66..9264d2e 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -306,13 +306,6 @@
public float brightnessDefault;
/**
- * @hide
- * True if Display#getRealSize and getRealMetrics should be constrained for Launcher, false
- * otherwise.
- */
- public boolean shouldConstrainMetricsForLauncher = false;
-
- /**
* The {@link RoundedCorners} if present, otherwise {@code null}.
*/
@Nullable
@@ -395,7 +388,6 @@
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
&& Objects.equals(roundedCorners, other.roundedCorners)
- && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher
&& installOrientation == other.installOrientation;
}
@@ -447,7 +439,6 @@
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
- shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher;
installOrientation = other.installOrientation;
}
@@ -505,7 +496,6 @@
for (int i = 0; i < numUserDisabledFormats; i++) {
userDisabledHdrTypes[i] = source.readInt();
}
- shouldConstrainMetricsForLauncher = source.readBoolean();
installOrientation = source.readInt();
}
@@ -561,7 +551,6 @@
for (int i = 0; i < userDisabledHdrTypes.length; i++) {
dest.writeInt(userDisabledHdrTypes[i]);
}
- dest.writeBoolean(shouldConstrainMetricsForLauncher);
dest.writeInt(installOrientation);
}
@@ -817,8 +806,6 @@
sb.append(brightnessMaximum);
sb.append(", brightnessDefault ");
sb.append(brightnessDefault);
- sb.append(", shouldConstrainMetricsForLauncher ");
- sb.append(shouldConstrainMetricsForLauncher);
sb.append(", installOrientation ");
sb.append(Surface.rotationToString(installOrientation));
sb.append("}");
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 88089b5..d4a8a16 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -167,16 +167,6 @@
public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
= "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
- /**
- * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app
- * prediction service is available. Our query of the service <em>availability</em> depends on
- * privileges that are only available in the system, even though the service itself would then
- * be available to the unbundled component. For now, we just include the query result as part of
- * the handover intent.
- * TODO: investigate whether the privileged query is necessary to determine the availability.
- */
- public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
- "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
/**
* Transition name for the first image preview.
@@ -985,6 +975,12 @@
}
@Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ViewPager viewPager = findViewById(R.id.profile_pager);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ea8589b..bfd8ff9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -157,8 +157,6 @@
/** See {@link #setRetainInOnStop}. */
private boolean mRetainInOnStop;
- protected static final int REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER = 20;
-
private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
@@ -1374,18 +1372,6 @@
.write();
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER:
- // Repeat the delegate's result as our own.
- setResult(resultCode, data);
- finish();
- break;
- default:
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
public void onActivityStarted(TargetInfo cti) {
// Do nothing
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index db41d33..1feb5d4 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -88,8 +88,8 @@
private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x040;
private static final int ALLOW_ASSOCIATIONS = 0x080;
// ALLOW_OVERRIDE_APP_RESTRICTIONS allows to use "allow-in-power-save-except-idle",
- // "allow-in-power-save", "allow-in-data-usage-save", "allow-unthrottled-location",
- // and "allow-ignore-location-settings".
+ // "allow-in-power-save", "allow-in-data-usage-save","allow-unthrottled-location",
+ // "allow-ignore-location-settings" and "allow-adas-location-settings".
private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
private static final int ALLOW_VENDOR_APEX = 0x400;
@@ -234,6 +234,10 @@
// without throttling, as read from the configuration files.
final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
+ // These are the packages that are allow-listed to be able to retrieve location when
+ // the location state is driver assistance only.
+ final ArrayMap<String, ArraySet<String>> mAllowAdasSettings = new ArrayMap<>();
+
// These are the packages that are white-listed to be able to retrieve location even when user
// location settings are off, for emergency purposes, as read from the configuration files.
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
@@ -394,6 +398,10 @@
return mAllowUnthrottledLocation;
}
+ public ArrayMap<String, ArraySet<String>> getAllowAdasLocationSettings() {
+ return mAllowAdasSettings;
+ }
+
public ArrayMap<String, ArraySet<String>> getAllowIgnoreLocationSettings() {
return mAllowIgnoreLocationSettings;
}
@@ -1007,6 +1015,34 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "allow-adas-location-settings" : {
+ if (allowOverrideAppRestrictions) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ String attributionTag = parser.getAttributeValue(null,
+ "attributionTag");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ ArraySet<String> tags = mAllowAdasSettings.get(pkgname);
+ if (tags == null || !tags.isEmpty()) {
+ if (tags == null) {
+ tags = new ArraySet<>(1);
+ mAllowAdasSettings.put(pkgname, tags);
+ }
+ if (!"*".equals(attributionTag)) {
+ if ("null".equals(attributionTag)) {
+ attributionTag = null;
+ }
+ tags.add(attributionTag);
+ }
+ }
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "allow-ignore-location-settings": {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/core/proto/android/os/appbackgroundrestrictioninfo.proto b/core/proto/android/os/appbackgroundrestrictioninfo.proto
new file mode 100644
index 0000000..8445641
--- /dev/null
+++ b/core/proto/android/os/appbackgroundrestrictioninfo.proto
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.os;
+
+option java_multiple_files = true;
+
+// This message is used for statsd logging and should be kept in sync with
+// frameworks/proto_logging/stats/atoms.proto
+/**
+ * Logs information about app background restrictions.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/AppRestrictionController.java
+ */
+message AppBackgroundRestrictionsInfo {
+ // the uid of the app.
+ optional int32 uid = 1;
+
+ enum RestrictionLevel {
+ LEVEL_UNKNOWN = 0;
+ LEVEL_UNRESTRICTED = 1;
+ LEVEL_EXEMPTED = 2;
+ LEVEL_ADAPTIVE_BUCKET = 3;
+ LEVEL_RESTRICTED_BUCKET = 4;
+ LEVEL_BACKGROUND_RESTRICTED = 5;
+ LEVEL_HIBERNATION = 6;
+ }
+ // indicates the app background restriction level.
+ optional RestrictionLevel restriction_level = 2;
+
+ enum Threshold {
+ THRESHOLD_UNKNOWN = 0;
+ THRESHOLD_RESTRICTED = 1; // app was background restricted by the system.
+ THRESHOLD_USER = 2; // app was background restricted by user action.
+ }
+ // indicates which threshold caused the app to be put into bg restriction.
+ optional Threshold threshold = 3;
+
+ enum StateTracker {
+ UNKNOWN_TRACKER = 0;
+ BATTERY_TRACKER = 1;
+ BATTERY_EXEMPTION_TRACKER = 2;
+ FGS_TRACKER = 3;
+ MEDIA_SESSION_TRACKER = 4;
+ PERMISSION_TRACKER = 5;
+ BROADCAST_EVENTS_TRACKER = 6;
+ BIND_SERVICE_EVENTS_TRACKER = 7;
+ }
+ // indicates the reason/tracker which caused the app to hit the threshold.
+ optional StateTracker tracker = 4;
+
+ message FgsTrackerInfo {
+ // indicates whether an fgs notification was visible for this app or not.
+ optional bool fgs_notification_visible = 1;
+ // total FGS duration for this app.
+ optional int64 fgs_duration = 2;
+ }
+ optional FgsTrackerInfo fgs_tracker_info = 5;
+
+ message BatteryTrackerInfo {
+ // total battery usage within last 24h (percentage)
+ optional int32 battery_24h = 1;
+ // background battery usage (percentage)
+ optional int32 battery_usage_background = 2;
+ // FGS battery usage (percentage)
+ optional int32 battery_usage_fgs = 3;
+ }
+ optional BatteryTrackerInfo battery_tracker_info = 6;
+
+ message BroadcastEventsTrackerInfo {
+ // the number of broadcasts sent by this app.
+ optional int32 broadcasts_sent = 1;
+ }
+ optional BroadcastEventsTrackerInfo broadcast_events_tracker_info = 7;
+
+ message BindServiceEventsTrackerInfo {
+ // the number of bind service requests by this app.
+ optional int32 bind_service_requests = 1;
+ }
+ optional BindServiceEventsTrackerInfo bind_service_events_tracker_info =
+ 8;
+
+ // The reasons listed below are defined in PowerExemptionManager.java
+ enum ExemptionReason {
+ // range 0-9 is reserved for default reasons
+ REASON_UNKNOWN = 0;
+ REASON_DENIED = 1;
+ REASON_OTHER = 2;
+ // range 10-49 is reserved for BG-FGS-launch allowed proc states
+ REASON_PROC_STATE_PERSISTENT = 10;
+ REASON_PROC_STATE_PERSISTENT_UI = 11;
+ REASON_PROC_STATE_TOP = 12;
+ REASON_PROC_STATE_BTOP = 13;
+ REASON_PROC_STATE_FGS = 14;
+ REASON_PROC_STATE_BFGS = 15;
+ // range 50-99 is reserved for BG-FGS-launch allowed reasons
+ REASON_UID_VISIBLE = 50;
+ REASON_SYSTEM_UID = 51;
+ REASON_ACTIVITY_STARTER = 52;
+ REASON_START_ACTIVITY_FLAG = 53;
+ REASON_FGS_BINDING = 54;
+ REASON_DEVICE_OWNER = 55;
+ REASON_PROFILE_OWNER = 56;
+ REASON_COMPANION_DEVICE_MANAGER = 57;
+ REASON_BACKGROUND_ACTIVITY_PERMISSION = 58;
+ REASON_BACKGROUND_FGS_PERMISSION = 59;
+ REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60;
+ REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61;
+ REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62;
+ REASON_DEVICE_DEMO_MODE = 63;
+ REASON_ALLOWLISTED_PACKAGE = 65;
+ REASON_APPOP = 66;
+ REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67;
+ REASON_OP_ACTIVATE_VPN = 68;
+ REASON_OP_ACTIVATE_PLATFORM_VPN = 69;
+ REASON_TEMP_ALLOWED_WHILE_IN_USE = 70;
+ REASON_CURRENT_INPUT_METHOD = 71;
+ // range 100-199 is reserved for public reasons
+ REASON_GEOFENCING = 100;
+ REASON_PUSH_MESSAGING = 101;
+ REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+ REASON_ACTIVITY_RECOGNITION = 103;
+ REASON_ACCOUNT_TRANSFER = 104;
+ // range 200-299 is reserved for broadcast actions
+ REASON_BOOT_COMPLETED = 200;
+ REASON_PRE_BOOT_COMPLETED = 201;
+ REASON_LOCKED_BOOT_COMPLETED = 202;
+ REASON_BLUETOOTH_BROADCAST = 203;
+ REASON_TIMEZONE_CHANGED = 204;
+ REASON_TIME_CHANGED = 205;
+ REASON_LOCALE_CHANGED = 206;
+ REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
+ REASON_REFRESH_SAFETY_SOURCES = 208;
+ // range 300-399 is reserved for other internal reasons
+ REASON_SYSTEM_ALLOW_LISTED = 300;
+ REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
+ REASON_ALARM_MANAGER_WHILE_IDLE = 302;
+ REASON_SERVICE_LAUNCH = 303;
+ REASON_KEY_CHAIN = 304;
+ REASON_PACKAGE_VERIFIER = 305;
+ REASON_SYNC_MANAGER = 306;
+ REASON_DOMAIN_VERIFICATION_V1 = 307;
+ REASON_DOMAIN_VERIFICATION_V2 = 308;
+ REASON_VPN = 309;
+ REASON_NOTIFICATION_SERVICE = 310;
+ REASON_PACKAGE_REPLACED = 311;
+ REASON_LOCATION_PROVIDER = 312;
+ REASON_MEDIA_BUTTON = 313;
+ REASON_EVENT_SMS = 314;
+ REASON_EVENT_MMS = 315;
+ REASON_SHELL = 316;
+ REASON_MEDIA_SESSION_CALLBACK = 317;
+ REASON_ROLE_DIALER = 318;
+ REASON_ROLE_EMERGENCY = 319;
+ REASON_SYSTEM_MODULE = 320;
+ REASON_CARRIER_PRIVILEGED_APP = 321;
+ // app requested to be exempt
+ REASON_OPT_OUT_REQUESTED = 1000;
+ }
+ // indicates if the app is exempt from background restrictions and the reason if applicable.
+ optional ExemptionReason exemption_reason = 9;
+
+ enum OptimizationLevel {
+ UNKNOWN = 0;
+ OPTIMIZED = 1;
+ BACKGROUND_RESTRICTED = 2;
+ NOT_OPTIMIZED = 3;
+ }
+ // the user choice for the optimization level of the app.
+ optional OptimizationLevel opt_level = 10;
+
+ enum TargetSdk {
+ SDK_UNKNOWN = 0;
+ SDK_PRE_S = 1;
+ SDK_S = 2;
+ SDK_T = 3;
+ }
+ // indicates the target sdk level for this app.
+ optional TargetSdk target_sdk = 11;
+
+ // indicates if the current device is a low ram device.
+ optional bool low_mem_device = 12;
+}
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7069e716..dd69fa0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2303,6 +2303,7 @@
<java-symbol type="drawable" name="scrubber_control_disabled_holo" />
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
+ <java-symbol type="drawable" name="progress_small_material" />
<java-symbol type="string" name="chooseUsbActivity" />
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
<java-symbol type="string" name="ext_media_badremoval_notification_title" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 3e261a7..50639be 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -157,7 +157,7 @@
.setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
.setIsForward(true).setAssistToken(assistToken)
.setShareableActivityToken(shareableActivityToken)
- .build();
+ .setTaskFragmentToken(new Binder()).build();
LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 1467fed..26d9628 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -110,6 +110,7 @@
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
private boolean mLaunchedFromBubble;
+ private IBinder mTaskFragmentToken;
LaunchActivityItemBuilder setIntent(Intent intent) {
mIntent = intent;
@@ -206,13 +207,18 @@
return this;
}
+ LaunchActivityItemBuilder setTaskFragmentToken(IBinder taskFragmentToken) {
+ mTaskFragmentToken = taskFragmentToken;
+ return this;
+ }
+
LaunchActivityItem build() {
return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
null /* activityClientController */, mShareableActivityToken,
- mLaunchedFromBubble);
+ mLaunchedFromBubble, mTaskFragmentToken);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index beadc446..8276d10 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -203,6 +203,7 @@
.setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
.setPendingNewIntents(referrerIntentList()).setIsForward(true)
.setAssistToken(new Binder()).setShareableActivityToken(new Binder())
+ .setTaskFragmentToken(new Binder())
.build();
writeAndPrepareForReading(item);
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index b006a16..8d3751e 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -345,7 +345,7 @@
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
- false /* launchedFromBubble */);
+ false /* launchedFromBubble */, null /* taskfragmentToken */);
}
@Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 449fb02..58a2073 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -273,6 +273,7 @@
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+ <permission name="android.permission.ACCESS_FPS_COUNTER"/>
<permission name="android.permission.MANAGE_GAME_MODE"/>
<permission name="android.permission.MANAGE_GAME_ACTIVITY" />
<permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index be21f4e..03b268d 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -5,3 +5,6 @@
# Framework-specific renames.
rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
+
+# for modules-utils-build dependency
+rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index c76fa96..01f5feb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -615,14 +615,22 @@
return null;
}
+ private void updateCallbackIfNecessary() {
+ updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
+ }
+
/**
* Notifies listeners about changes to split states if necessary.
+ *
+ * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
+ * callback should be deferred until all the
+ * organized activities have been created.
*/
- private void updateCallbackIfNecessary() {
+ private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
if (mEmbeddingCallback == null) {
return;
}
- if (!allActivitiesCreated()) {
+ if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
return;
}
List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -838,6 +846,36 @@
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
+ public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ final IBinder activityToken = activity.getActivityToken();
+ final IBinder initialTaskFragmentToken = ActivityThread.currentActivityThread()
+ .getActivityClient(activityToken).mInitialTaskFragmentToken;
+ // If the activity is not embedded, then it will not have an initial task fragment token
+ // so no further action is needed.
+ if (initialTaskFragmentToken == null) {
+ return;
+ }
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .mContainers;
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (!container.hasActivity(activityToken)
+ && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
+ // The onTaskFragmentInfoChanged callback containing this activity has not
+ // reached the client yet, so add the activity to the pending appeared
+ // activities and send a split info callback to the client before
+ // {@link Activity#onCreate} is called.
+ container.addPendingAppearedActivity(activity);
+ updateCallbackIfNecessary(
+ false /* deferCallbackUntilAllActivitiesCreated */);
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e49af41..9a12669 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -120,7 +120,7 @@
}
ActivityStack toActivityStack() {
- return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0);
+ return new ActivityStack(collectActivities(), isEmpty());
}
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
diff --git a/libs/WindowManager/Jetpack/tests/OWNERS b/libs/WindowManager/Jetpack/tests/OWNERS
index f2c3388..ac522b2 100644
--- a/libs/WindowManager/Jetpack/tests/OWNERS
+++ b/libs/WindowManager/Jetpack/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
# includes OWNERS from parent directories
charlesccchen@google.com
diegovela@google.com
diff --git a/libs/WindowManager/Shell/res/drawable/home_icon.xml b/libs/WindowManager/Shell/res/drawable/home_icon.xml
new file mode 100644
index 0000000..1669d01
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gravity="center">
+ <item android:gravity="center">
+ <shape
+ android:shape="oval">
+ <stroke
+ android:color="@color/tv_pip_edu_text_home_icon"
+ android:width="1sp" />
+ <solid android:color="@android:color/transparent" />
+ <size
+ android:width="@dimen/pip_menu_edu_text_home_icon_outline"
+ android:height="@dimen/pip_menu_edu_text_home_icon_outline"/>
+ </shape>
+ </item>
+ <item
+ android:width="@dimen/pip_menu_edu_text_home_icon"
+ android:height="@dimen/pip_menu_edu_text_home_icon"
+ android:gravity="center">
+ <vector
+ android:width="24sp"
+ android:height="24sp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_edu_text_home_icon"
+ android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
+ </vector>
+ </item>
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
new file mode 100644
index 0000000..0c62792
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_background_corner_radius" />
+ <solid android:color="@color/tv_pip_menu_background"/>
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_background"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 9bc0311..846fdb3 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -14,9 +14,20 @@
~ 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:shape="rectangle">
- <corners android:radius="@dimen/pip_menu_border_radius" />
- <stroke android:width="@dimen/pip_menu_border_width"
- android:color="@color/tv_pip_menu_focus_border" />
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+ <item android:state_activated="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_focus_border" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_background"/>
+ </shape>
+ </item>
+</selector>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index b826d03..dbd5a9b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,26 +16,41 @@
-->
<!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tv_pip_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:id="@+id/tv_pip_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|top">
+
+ <View
+ android:id="@+id/tv_pip"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/pip_menu_outer_space"
+ android:layout_marginStart="@dimen/pip_menu_outer_space"
+ android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
<ScrollView
android:id="@+id/tv_pip_menu_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
+ android:layout_alignTop="@+id/tv_pip"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip"
android:scrollbars="none"
- android:layout_margin="@dimen/pip_menu_outer_space"
android:visibility="gone"/>
<HorizontalScrollView
android:id="@+id/tv_pip_menu_horizontal_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_alignTop="@+id/tv_pip"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip"
android:gravity="center_vertical"
- android:scrollbars="none"
- android:layout_margin="@dimen/pip_menu_outer_space">
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/tv_pip_menu_action_buttons"
@@ -89,10 +104,44 @@
</HorizontalScrollView>
<View
+ android:id="@+id/tv_pip_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/pip_menu_outer_space_frame"
+ android:layout_marginStart="@dimen/pip_menu_outer_space_frame"
+ android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_border"/>
+
+ <FrameLayout
+ android:id="@+id/tv_pip_menu_edu_text_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip_menu_frame"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:background="@color/tv_pip_menu_background"
+ android:clipChildren="true">
+
+ <TextView
+ android:id="@+id/tv_pip_menu_edu_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/pip_menu_edu_text_view_height"
+ android:layout_gravity="bottom|center"
+ android:gravity="center"
+ android:paddingBottom="@dimen/pip_menu_border_width"
+ android:text="@string/pip_edu_text"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:scrollHorizontally="true"
+ android:textAppearance="@style/TvPipEduText"/>
+ </FrameLayout>
+
+ <View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:alpha="0"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
new file mode 100644
index 0000000..5af4020
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Layout for the back surface of the PiP menu -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_background"
+ android:elevation="@dimen/pip_menu_elevation"/>
+</FrameLayout>
+
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 558ec51..776b18e 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -22,7 +22,12 @@
<dimen name="pip_menu_button_margin">4dp</dimen>
<dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<dimen name="pip_menu_border_width">4dp</dimen>
- <dimen name="pip_menu_border_radius">4dp</dimen>
+ <integer name="pip_menu_fade_animation_duration">500</integer>
+ <!-- The pip menu front border corner radius is 2dp smaller than
+ the background corner radius to hide the background from
+ showing through. -->
+ <dimen name="pip_menu_border_corner_radius">4dp</dimen>
+ <dimen name="pip_menu_background_corner_radius">6dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
<!-- outer space minus border width -->
@@ -30,5 +35,14 @@
<dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
+
+ <dimen name="pip_menu_elevation">1dp</dimen>
+
+ <dimen name="pip_menu_edu_text_view_height">24dp</dimen>
+ <dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
+ <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
+ <integer name="pip_edu_text_show_duration_ms">10500</integer>
+ <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
+ <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 64b146e..fa90fe3 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -23,4 +23,8 @@
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
<color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>
-</resources>
\ No newline at end of file
+ <color name="tv_pip_menu_background">#1E232C</color>
+
+ <color name="tv_pip_edu_text">#99D2E3FC</color>
+ <color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 7733201..19f7c3e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -47,4 +47,13 @@
<item name="android:layout_width">96dp</item>
<item name="android:layout_height">48dp</item>
</style>
+
+ <style name="TvPipEduText">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textSize">10sp</item>
+ <item name="android:lineSpacingExtra">4sp</item>
+ <item name="android:lineHeight">16sp</item>
+ <item name="android:textColor">@color/tv_pip_edu_text</item>
+ </style>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
new file mode 100644
index 0000000..6efdd57
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+/** An ImageSpan for a Drawable that is centered vertically in the line. */
+public class CenteredImageSpan extends ImageSpan {
+
+ private Drawable mDrawable;
+
+ public CenteredImageSpan(Drawable drawable) {
+ super(drawable);
+ }
+
+ @Override
+ public int getSize(
+ Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetrics) {
+ final Drawable drawable = getCachedDrawable();
+ final Rect rect = drawable.getBounds();
+
+ if (fontMetrics != null) {
+ Paint.FontMetricsInt paintFontMetrics = paint.getFontMetricsInt();
+ fontMetrics.ascent = paintFontMetrics.ascent;
+ fontMetrics.descent = paintFontMetrics.descent;
+ fontMetrics.top = paintFontMetrics.top;
+ fontMetrics.bottom = paintFontMetrics.bottom;
+ }
+
+ return rect.right;
+ }
+
+ @Override
+ public void draw(
+ Canvas canvas,
+ CharSequence text,
+ int start,
+ int end,
+ float x,
+ int top,
+ int y,
+ int bottom,
+ Paint paint) {
+ final Drawable drawable = getCachedDrawable();
+ canvas.save();
+ final int transY = (bottom - drawable.getBounds().bottom) / 2;
+ canvas.translate(x, transY);
+ drawable.draw(canvas);
+ canvas.restore();
+ }
+
+ private Drawable getCachedDrawable() {
+ if (mDrawable == null) {
+ mDrawable = getDrawable();
+ }
+ return mDrawable;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1aefd77..21d5d40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -150,6 +151,10 @@
mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds);
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+ mKeepClearAlgorithm.setPipPermanentDecorInsets(
+ mTvPipBoundsState.getPipMenuPermanentDecorInsets());
+ mKeepClearAlgorithm.setPipTemporaryDecorInsets(
+ mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,
@@ -340,6 +345,7 @@
final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
final float expandedRatio =
mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
+ final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
final Size expandedSize;
if (expandedRatio == 0) {
@@ -352,7 +358,8 @@
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
+ int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+ - pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
@@ -374,7 +381,8 @@
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
+ int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+ - pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
if (DEBUG) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 9865548..ea07499 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
@@ -60,7 +61,8 @@
private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity;
private @Nullable Size mTvExpandedSize;
-
+ private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+ private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) {
super(context);
@@ -159,4 +161,19 @@
return mIsTvExpandedPipSupported;
}
+ public void setPipMenuPermanentDecorInsets(@NonNull Insets permanentInsets) {
+ mPipMenuPermanentDecorInsets = permanentInsets;
+ }
+
+ public @NonNull Insets getPipMenuPermanentDecorInsets() {
+ return mPipMenuPermanentDecorInsets;
+ }
+
+ public void setPipMenuTemporaryDecorInsets(@NonNull Insets temporaryDecorInsets) {
+ mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
+ }
+
+ public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+ return mPipMenuTemporaryDecorInsets;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46b8e60..0e1f5a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -115,6 +115,7 @@
private int mPipForceCloseDelay;
private int mResizeAnimationDuration;
+ private int mEduTextWindowExitAnimationDurationMs;
public static Pip create(
Context context,
@@ -323,11 +324,15 @@
}
}
+ private void updatePinnedStackBounds() {
+ updatePinnedStackBounds(mResizeAnimationDuration);
+ }
+
/**
* Update the PiP bounds based on the state of the PiP and keep clear areas.
* Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
*/
- private void updatePinnedStackBounds() {
+ private void updatePinnedStackBounds(int animationDuration) {
if (mState == STATE_NO_PIP) {
return;
}
@@ -353,23 +358,26 @@
mUnstashRunnable = null;
}
if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
- mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+ mUnstashRunnable = () -> {
+ movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
+ };
mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
}
}
/** Animates the PiP to the given bounds. */
private void movePinnedStackTo(Rect bounds) {
+ movePinnedStackTo(bounds, mResizeAnimationDuration);
+ }
+
+ /** Animates the PiP to the given bounds with the given animation duration. */
+ private void movePinnedStackTo(Rect bounds, int animationDuration) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
}
mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
- mResizeAnimationDuration, rect -> {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePinnedStack() animation done", TAG);
- }
+ animationDuration, rect -> {
mTvPipMenuController.updateExpansionState();
});
}
@@ -408,6 +416,11 @@
onPipDisappeared();
}
+ @Override
+ public void closeEduText() {
+ updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
+ }
+
private void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
@@ -457,6 +470,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(true);
}
@Override
@@ -465,6 +479,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(false);
}
@Override
@@ -476,6 +491,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(false);
}
private void setState(@State int state) {
@@ -491,6 +507,8 @@
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+ mEduTextWindowExitAnimationDurationMs =
+ res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 5ac7a72..9ede443 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.tv
+import android.graphics.Insets
import android.graphics.Point
import android.graphics.Rect
import android.util.Size
@@ -94,6 +95,13 @@
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
private var lastStashTime: Long = Long.MIN_VALUE
+ /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
+ * decorations are relevant for calculating intersecting keep clear areas */
+ private var pipPermanentDecorInsets = Insets.NONE
+ /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
+ * decorations are not relevant for calculating intersecting keep clear areas */
+ private var pipTemporaryDecorInsets = Insets.NONE
+
/**
* Calculates the position the PiP should be placed at, taking into consideration the
* given keep clear areas.
@@ -120,20 +128,29 @@
): Placement {
val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
- val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
+ val pipSizeWithAllDecors = addDecors(pipSize)
+ val pipAnchorBoundsWithAllDecors =
+ getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
+
+ val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
val result = calculatePipPositionTransformed(
- pipAnchorBounds,
+ pipAnchorBoundsWithPermanentDecors,
transformedRestrictedAreas,
transformedUnrestrictedAreas
)
- val screenSpaceBounds = fromTransformedSpace(result.bounds)
+ val pipBounds = removePermanentDecors(fromTransformedSpace(result.bounds))
+ val anchorBounds = removePermanentDecors(fromTransformedSpace(result.anchorBounds))
+ val unstashedDestBounds = result.unstashDestinationBounds?.let {
+ removePermanentDecors(fromTransformedSpace(it))
+ }
+
return Placement(
- screenSpaceBounds,
- fromTransformedSpace(result.anchorBounds),
- getStashType(screenSpaceBounds, movementBounds),
- result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+ pipBounds,
+ anchorBounds,
+ getStashType(pipBounds, movementBounds),
+ unstashedDestBounds,
result.unstashTime
)
}
@@ -447,6 +464,16 @@
transformedMovementBounds = toTransformedSpace(movementBounds)
}
+ fun setPipPermanentDecorInsets(insets: Insets) {
+ if (pipPermanentDecorInsets == insets) return
+ pipPermanentDecorInsets = insets
+ }
+
+ fun setPipTemporaryDecorInsets(insets: Insets) {
+ if (pipTemporaryDecorInsets == insets) return
+ pipTemporaryDecorInsets = insets
+ }
+
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -735,6 +762,35 @@
return horizontal && vertical
}
+ /**
+ * Adds space around [size] to leave space for decorations that will be drawn around the pip
+ */
+ private fun addDecors(size: Size): Size {
+ val bounds = Rect(0, 0, size.width, size.height)
+ bounds.inset(pipPermanentDecorInsets)
+ bounds.inset(pipTemporaryDecorInsets)
+
+ return Size(bounds.width(), bounds.height())
+ }
+
+ /**
+ * Removes the space that was reserved for permanent decorations around the pip
+ */
+ private fun removePermanentDecors(bounds: Rect): Rect {
+ val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
+ bounds.inset(pipDecorReverseInsets)
+ return bounds
+ }
+
+ /**
+ * Removes the space that was reserved for temporary decorations around the pip
+ */
+ private fun removeTemporaryDecors(bounds: Rect): Rect {
+ val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
+ bounds.inset(pipDecorReverseInsets)
+ return bounds
+ }
+
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 35c34ac..7b8dcf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -25,13 +25,17 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.RemoteException;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
@@ -53,15 +57,20 @@
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
private static final boolean DEBUG = TvPipController.DEBUG;
+ private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
private final Context mContext;
private final SystemWindows mSystemWindows;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
+ private final int mPipMenuBorderWidth;
+ private final int mPipEduTextShowDurationMs;
+ private final int mPipEduTextHeight;
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
+ private View mPipBackgroundView;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
@@ -74,6 +83,7 @@
private RemoteAction mCloseAction;
private SyncRtSurfaceTransactionApplier mApplier;
+ private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF();
RectF mTmpDestinationRectF = new RectF();
Matrix mMoveTransform = new Matrix();
@@ -91,6 +101,7 @@
if (DEBUG) e.printStackTrace();
}
};
+ private final Runnable mCloseEduTextRunnable = this::closeEduText;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +124,13 @@
mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged);
+
+ mPipEduTextShowDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_show_duration_ms);
+ mPipEduTextHeight = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mPipMenuBorderWidth = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_border_width);
}
void setDelegate(Delegate delegate) {
@@ -138,24 +156,63 @@
}
mLeash = leash;
- attachPipMenuView();
+ attachPipMenu();
}
- private void attachPipMenuView() {
+ private void attachPipMenu() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: attachPipMenuView()", TAG);
+ "%s: attachPipMenu()", TAG);
}
if (mPipMenuView != null) {
- detachPipMenuView();
+ detachPipMenu();
}
+ attachPipBackgroundView();
+ attachPipMenuView();
+
+ mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
+ -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
+ mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+ }
+
+ private void attachPipMenuView() {
mPipMenuView = new TvPipMenuView(mContext);
mPipMenuView.setListener(this);
- mSystemWindows.addView(mPipMenuView,
- getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
- 0, SHELL_ROOT_LAYER_PIP);
+ setUpViewSurfaceZOrder(mPipMenuView, 1);
+ addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+ }
+
+ private void attachPipBackgroundView() {
+ mPipBackgroundView = LayoutInflater.from(mContext)
+ .inflate(R.layout.tv_pip_menu_background, null);
+ setUpViewSurfaceZOrder(mPipBackgroundView, -1);
+ addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
+ }
+
+ private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
+ v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+
+ private void addPipMenuViewToSystemWindows(View v, String title) {
+ mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
+ 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+ }
+
+ void notifyPipAnimating(boolean animating) {
+ mPipMenuView.setEduTextActive(!animating);
}
void showMovementMenuOnly() {
@@ -171,8 +228,7 @@
@Override
public void showMenu() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMenu()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
}
mInMoveMode = false;
mCloseAfterExitMoveMenu = false;
@@ -183,27 +239,31 @@
if (mPipMenuView == null) {
return;
}
- Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
- mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
- MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+ maybeCloseEduText();
maybeUpdateMenuViewActions();
updateExpansionState();
- SurfaceControl menuSurfaceControl = getSurfaceControl();
- if (menuSurfaceControl != null) {
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
- t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
- t.apply();
- }
grantPipMenuFocus(true);
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
- mPipMenuView.showButtonMenu();
+ mPipMenuView.showButtonsMenu();
}
}
+ private void maybeCloseEduText() {
+ if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
+ mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+ mCloseEduTextRunnable.run();
+ }
+ }
+
+ private void closeEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mPipMenuView.hideEduText();
+ mDelegate.closeEduText();
+ }
+
void updateGravity(int gravity) {
mPipMenuView.showMovementHints(gravity);
}
@@ -214,12 +274,8 @@
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
}
- private Rect getMenuBounds(Rect pipBounds) {
- int extraSpaceInPx = mContext.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
- Rect menuBounds = new Rect(pipBounds);
- menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
- return menuBounds;
+ private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
+ return mPipMenuView.getPipMenuContainerBounds(pipBounds);
}
void closeMenu() {
@@ -227,11 +283,12 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
}
+
if (mPipMenuView == null) {
return;
}
- mPipMenuView.hideAll();
+ mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
}
@@ -266,7 +323,7 @@
}
if (mInMoveMode) {
mInMoveMode = false;
- mPipMenuView.showButtonMenu();
+ mPipMenuView.showButtonsMenu();
return true;
}
return false;
@@ -287,7 +344,8 @@
@Override
public void detach() {
closeMenu();
- detachPipMenuView();
+ mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+ detachPipMenu();
mLeash = null;
}
@@ -346,20 +404,15 @@
@Override
public boolean isMenuVisible() {
- boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: isMenuVisible: %b", TAG, isVisible);
- }
- return isVisible;
+ return true;
}
/**
* Does an immediate window crop of the PiP menu.
*/
@Override
- public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
- @android.annotation.Nullable SurfaceControl.Transaction t,
+ public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +426,36 @@
return;
}
- SurfaceControl surfaceControl = getSurfaceControl();
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
- .withWindowCrop(getMenuBounds(destinationBounds))
+ final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
+
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
+ .withWindowCrop(menuBounds)
.build();
+
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+ .withWindowCrop(menuBounds)
+ .build();
+
+ // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+ // animations of the pip surface with the content of the front and back menu surfaces
+ mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && t != null) {
- SyncRtSurfaceTransactionApplier.SurfaceParams
+ final SyncRtSurfaceTransactionApplier.SurfaceParams
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(t)
.build();
- mApplier.scheduleApply(params, pipParams);
+ mApplier.scheduleApply(frontParams, pipParams);
} else {
- mApplier.scheduleApply(params);
+ mApplier.scheduleApply(frontParams);
}
}
- private SurfaceControl getSurfaceControl() {
- return mSystemWindows.getViewSurface(mPipMenuView);
+ private SurfaceControl getSurfaceControl(View v) {
+ return mSystemWindows.getViewSurface(v);
}
@Override
@@ -412,44 +477,52 @@
return;
}
- Rect menuDestBounds = getMenuBounds(pipDestBounds);
- Rect mTmpSourceBounds = new Rect();
+ final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
+ final Rect tmpSourceBounds = new Rect();
// If there is no pip leash supplied, that means the PiP leash is already finalized
// resizing and the PiP menu is also resized. We then want to do a scale from the current
// new menu bounds.
if (pipLeash != null && transaction != null) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+ "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
}
- mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+ mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
} else {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: mTmpSourceBounds based on menu width and height", TAG);
+ "%s: tmpSourceBounds based on menu width and height", TAG);
}
- mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+ tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
}
- mTmpSourceRectF.set(mTmpSourceBounds);
+ mTmpSourceRectF.set(tmpSourceBounds);
mTmpDestinationRectF.set(menuDestBounds);
- mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
- SurfaceControl surfaceControl = getSurfaceControl();
- SyncRtSurfaceTransactionApplier.SurfaceParams params =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceControl)
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
.withMatrix(mMoveTransform)
.build();
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+ .withMatrix(mMoveTransform)
+ .build();
+
+ // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+ // animations of the pip surface with the content of the front and back menu surfaces
+ mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && transaction != null) {
- SyncRtSurfaceTransactionApplier.SurfaceParams
- pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(transaction)
.build();
- mApplier.scheduleApply(params, pipParams);
+ mApplier.scheduleApply(frontParams, pipParams);
} else {
- mApplier.scheduleApply(params);
+ mApplier.scheduleApply(frontParams);
}
if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +543,40 @@
if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
}
+ if (mBackgroundApplier == null) {
+ mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
+ }
return true;
}
- private void detachPipMenuView() {
- if (mPipMenuView == null) {
- return;
+ private void detachPipMenu() {
+ if (mPipMenuView != null) {
+ mApplier = null;
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
}
- mApplier = null;
- mSystemWindows.removeView(mPipMenuView);
- mPipMenuView = null;
+ if (mPipBackgroundView != null) {
+ mBackgroundApplier = null;
+ mSystemWindows.removeView(mPipBackgroundView);
+ mPipBackgroundView = null;
+ }
}
@Override
public void updateMenuBounds(Rect destinationBounds) {
- Rect menuBounds = getMenuBounds(destinationBounds);
+ final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
}
+ mSystemWindows.updateViewLayout(mPipBackgroundView,
+ getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
+
if (mPipMenuView != null) {
mPipMenuView.updateLayout(destinationBounds);
}
@@ -538,6 +622,8 @@
void onMenuClosed();
+ void closeEduText();
+
void closePip();
}
@@ -555,4 +641,30 @@
"%s: Unable to update focus, %s", TAG, e);
}
}
+
+ private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
+ private final View mView;
+ private final int mZOrder;
+
+ PipMenuSurfaceChangedCallback(View v, int zOrder) {
+ mView = v;
+ mZOrder = zOrder;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl(mView);
+ if (sc != null) {
+ t.setRelativeLayer(sc, mLeash, mZOrder);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 9529d04..5b0db8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,11 +25,17 @@
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
+import android.animation.ValueAnimator;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -40,6 +46,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -49,6 +56,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -67,6 +75,15 @@
private final LinearLayout mActionButtonsContainer;
private final View mMenuFrameView;
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final View mPipFrameView;
+ private final View mPipView;
+ private final TextView mEduTextView;
+ private final View mEduTextContainerView;
+ private final int mPipMenuOuterSpace;
+ private final int mPipMenuBorderWidth;
+ private final int mEduTextFadeExitAnimationDurationMs;
+ private final int mEduTextSlideExitAnimationDurationMs;
+ private int mEduTextHeight;
private final ImageView mArrowUp;
private final ImageView mArrowRight;
@@ -76,11 +93,13 @@
private final ViewGroup mScrollView;
private final ViewGroup mHorizontalScrollView;
- private Rect mCurrentBounds;
+ private Rect mCurrentPipBounds;
private final TvPipMenuActionButton mExpandButton;
private final TvPipMenuActionButton mCloseButton;
+ private final int mPipMenuFadeAnimationDuration;
+
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -116,21 +135,86 @@
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
+ mPipFrameView = findViewById(R.id.tv_pip_border);
+ mPipView = findViewById(R.id.tv_pip);
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
+
+ mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
+ mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+
+ mPipMenuFadeAnimationDuration = context.getResources()
+ .getInteger(R.integer.pip_menu_fade_animation_duration);
+ mPipMenuOuterSpace = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+ mPipMenuBorderWidth = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+ mEduTextHeight = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mEduTextFadeExitAnimationDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
+ mEduTextSlideExitAnimationDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+
+ initEduText();
}
- void updateLayout(Rect updatedBounds) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
- boolean previouslyVertical =
- mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
- boolean vertical = updatedBounds.height() > updatedBounds.width();
+ void initEduText() {
+ final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+ final SpannableString spannableString = new SpannableString(eduText);
+ Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+ .ifPresent(annotation -> {
+ final Drawable icon =
+ getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+ if (icon != null) {
+ icon.mutate();
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ spannableString.setSpan(new CenteredImageSpan(icon),
+ eduText.getSpanStart(annotation),
+ eduText.getSpanEnd(annotation),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ });
- mCurrentBounds = updatedBounds;
+ mEduTextView.setText(spannableString);
+ }
+
+ void setEduTextActive(boolean active) {
+ mEduTextView.setSelected(active);
+ }
+
+ void hideEduText() {
+ final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
+ heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
+ heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+ heightAnimation.addUpdateListener(animator -> {
+ mEduTextHeight = (int) animator.getAnimatedValue();
+ });
+ mEduTextView.animate()
+ .alpha(0f)
+ .setInterpolator(TvPipInterpolators.EXIT)
+ .setDuration(mEduTextFadeExitAnimationDurationMs)
+ .withEndAction(() -> {
+ mEduTextContainerView.setVisibility(GONE);
+ }).start();
+ heightAnimation.start();
+ }
+
+ void updateLayout(Rect updatedPipBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
+
+ boolean previouslyVertical =
+ mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
+ boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
+
+ mCurrentPipBounds = updatedPipBounds;
+
+ updatePipFrameBounds();
+
if (previouslyVertical == vertical) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -158,6 +242,38 @@
mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
}
+ Rect getPipMenuContainerBounds(Rect pipBounds) {
+ final Rect menuUiBounds = new Rect(pipBounds);
+ menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+ menuUiBounds.bottom += mEduTextHeight;
+ return menuUiBounds;
+ }
+
+ /**
+ * Update mPipFrameView's bounds according to the new pip window bounds. We can't
+ * make mPipFrameView match_parent, because the pip menu might contain other content around
+ * the pip window (e.g. edu text).
+ * TvPipMenuView needs to account for this so that it can draw a white border around the whole
+ * pip menu when it gains focus.
+ */
+ private void updatePipFrameBounds() {
+ final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
+ if (pipFrameParams != null) {
+ pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
+ pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
+ mPipFrameView.setLayoutParams(pipFrameParams);
+ }
+
+ final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
+ if (pipViewParams != null) {
+ pipViewParams.width = mCurrentPipBounds.width();
+ pipViewParams.height = mCurrentPipBounds.height();
+ mPipView.setLayoutParams(pipViewParams);
+ }
+
+
+ }
+
void setListener(@Nullable Listener listener) {
mListener = listener;
}
@@ -184,30 +300,32 @@
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
}
- showMenuButtons(false);
+ showButtonsMenu(false);
showMovementHints(gravity);
- showMenuFrame(true);
+ setFrameHighlighted(true);
}
- void showButtonMenu() {
+ void showButtonsMenu() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showButtonsMenu()", TAG);
}
- showMenuButtons(true);
+ showButtonsMenu(true);
hideMovementHints();
- showMenuFrame(true);
+ setFrameHighlighted(true);
}
/**
* Hides all menu views, including the menu frame.
*/
- void hideAll() {
+ void hideAllUserControls() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideAllUserControls()", TAG);
}
- showMenuButtons(false);
+ showButtonsMenu(false);
hideMovementHints();
- showMenuFrame(false);
+ setFrameHighlighted(false);
}
private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +335,7 @@
view.animate()
.alpha(alpha)
.setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
- .setDuration(500)
+ .setDuration(mPipMenuFadeAnimationDuration)
.withStartAction(() -> {
if (alpha != 0) {
view.setVisibility(VISIBLE);
@@ -230,15 +348,6 @@
});
}
- boolean isVisible() {
- return mMenuFrameView.getAlpha() != 0f
- || mActionButtonsContainer.getAlpha() != 0f
- || mArrowUp.getAlpha() != 0f
- || mArrowRight.getAlpha() != 0f
- || mArrowDown.getAlpha() != 0f
- || mArrowLeft.getAlpha() != 0f;
- }
-
void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) {
if (DEBUG) {
@@ -423,18 +532,18 @@
}
/**
- * Show or hide the pip user actions.
+ * Show or hide the pip buttons menu.
*/
- public void showMenuButtons(boolean show) {
+ public void showButtonsMenu(boolean show) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMenuButtons: %b", TAG, show);
+ "%s: showUserActions: %b", TAG, show);
}
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
}
- private void showMenuFrame(boolean show) {
- animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+ private void setFrameHighlighted(boolean highlighted) {
+ mMenuFrameView.setActivated(highlighted);
}
interface Listener {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index a244d14..f4efc37 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
# includes OWNERS from parent directories
natanieljr@google.com
pablogamito@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index fb404b9..684e5cad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
+import android.view.WindowInsets
+import android.view.WindowManager
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
@@ -59,6 +61,16 @@
}
}
transitions {
+ // Swipe & wait for the notification shade to expand so all can be seen
+ val wm = context.getSystemService(WindowManager::class.java)
+ val metricInsets = wm.getCurrentWindowMetrics().windowInsets
+ val insets = metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.statusBars()
+ or WindowInsets.Type.displayCutout())
+ device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4)
+ device.waitForIdle(2000)
+ instrumentation.uiAutomation.syncInputTransactions()
+
val notification = device.wait(Until.findObject(
By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
notification?.click() ?: error("Notification not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3fe6f02..9a8c894 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -84,7 +85,7 @@
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
/** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
+ @Presubmit
@Test
override fun pipLayerExpands() = super.pipLayerExpands()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 5fc80db..9f3fcea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -111,7 +111,7 @@
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
- @Presubmit
+ @FlakyTest(bugId = 228012337)
@Test
fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 6af01e2..c1ee1a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -33,7 +33,6 @@
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,11 +68,6 @@
private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = false) {
setup {
@@ -135,15 +129,30 @@
/**
* Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
*/
- @Presubmit
- @Test
- open fun pipLayerRotates_StartingBounds() {
+ private fun pipLayerRotates_StartingBounds_internal() {
testSpec.assertLayersStart {
visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
}
}
/**
+ * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_StartingBounds() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ pipLayerRotates_StartingBounds_internal()
+ }
+
+ @FlakyTest(bugId = 228024285)
+ @Test
+ fun pipLayerRotates_StartingBounds_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ pipLayerRotates_StartingBounds_internal()
+ }
+
+ /**
* Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
*/
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 81403d0..e40f2bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -47,7 +47,6 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-@FlakyTest(bugId = 218604389)
open class SetRequestedOrientationWhilePinnedTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
index d743dff..6cd93ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
@@ -22,7 +22,6 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
-import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -116,24 +115,20 @@
private Notification.Builder getNotificationBuilder(int id) {
Person chatBot = new Person.Builder()
.setBot(true)
- .setName("BubbleBot")
+ .setName("BubbleChat")
.setImportant(true)
.build();
-
- RemoteInput remoteInput = new RemoteInput.Builder("key")
- .setLabel("Reply")
- .build();
-
String shortcutId = "BubbleChat";
return new Notification.Builder(mContext, CHANNEL_ID)
.setChannelId(CHANNEL_ID)
.setShortcutId(shortcutId)
+ .setContentTitle("BubbleChat")
.setContentIntent(PendingIntent.getActivity(mContext, 0,
new Intent(mContext, LaunchBubbleActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT))
.setStyle(new Notification.MessagingStyle(chatBot)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello? This is bubble: " + id,
+ .setConversationTitle("BubbleChat")
+ .addMessage("BubbleChat",
SystemClock.currentThreadTimeMillis() - 300000, chatBot)
.addMessage("Is it me, " + id + ", you're looking for?",
SystemClock.currentThreadTimeMillis(), chatBot)
diff --git a/location/java/android/location/LocationDeviceConfig.java b/location/java/android/location/LocationDeviceConfig.java
index c55eed9..7d22681 100644
--- a/location/java/android/location/LocationDeviceConfig.java
+++ b/location/java/android/location/LocationDeviceConfig.java
@@ -24,6 +24,30 @@
public final class LocationDeviceConfig {
/**
+ * Package/tag combinations that are allowlisted for ignoring location settings (may retrieve
+ * location even when user location settings are off), for advanced driver-assistance systems
+ * only.
+ *
+ * <p>Package/tag combinations are separated by commas (","), and with in each combination is a
+ * package name followed by 0 or more attribution tags, separated by semicolons (";"). If a
+ * package is followed by 0 attribution tags, this is interpreted the same as the wildcard
+ * value. There are two special interpreted values for attribution tags, the wildcard value
+ * ("*") which represents all attribution tags, and the null value ("null"), which is converted
+ * to the null string (since attribution tags may be null). This format implies that attribution
+ * tags which should be on this list may not contain semicolons.
+ *
+ * <p>Examples of valid entries:
+ *
+ * <ul>
+ * <li>android
+ * <li>android;*
+ * <li>android;*,com.example.app;null;my_attr
+ * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
+ * </ul>
+ */
+ public static final String ADAS_SETTINGS_ALLOWLIST = "adas_settings_allowlist";
+
+ /**
* Package/tag combinations that are allowedlisted for ignoring location settings (may retrieve
* location even when user location settings are off, and may ignore throttling, etc), for
* emergency purposes only.
@@ -39,10 +63,10 @@
* <p>Examples of valid entries:
*
* <ul>
- * <li>android</li>
- * <li>android;*</li>
- * <li>android;*,com.example.app;null;my_attr</li>
- * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr</li>
+ * <li>android
+ * <li>android;*
+ * <li>android;*,com.example.app;null;my_attr
+ * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
* </ul>
*/
public static final String IGNORE_SETTINGS_ALLOWLIST = "ignore_settings_allowlist";
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 491a5cd..e7eda3e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5898,14 +5898,8 @@
*/
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public void setWiredDeviceConnectionState(int device, int state, String address,
- String name) {
- final IAudioService service = getService();
- int role = isOutputDevice(device)
- ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(
- role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address,
- name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/);
+ public void setWiredDeviceConnectionState(int device, int state, String address, String name) {
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, address, name);
setWiredDeviceConnectionState(attributes, state);
}
diff --git a/media/tests/aidltests/Android.bp b/media/tests/aidltests/Android.bp
index c3d5fa2..7a25b6d 100644
--- a/media/tests/aidltests/Android.bp
+++ b/media/tests/aidltests/Android.bp
@@ -33,7 +33,6 @@
libs: [
"framework",
],
- sdk_version: "current",
platform_apis: true,
test_suites: ["device-tests"],
certificate: "platform",
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index c2e36b7..e1a2e8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -28,6 +28,7 @@
import android.util.Log;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -239,13 +240,24 @@
/**
* Dispatch a change in the about-to-connect device. See
- * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
+ * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information.
*/
- public void dispatchAboutToConnectDeviceChanged(
- @Nullable String deviceName,
+ public void dispatchAboutToConnectDeviceAdded(
+ @NonNull String deviceAddress,
+ @NonNull String deviceName,
@Nullable Drawable deviceIcon) {
for (DeviceCallback callback : getCallbacks()) {
- callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
+ callback.onAboutToConnectDeviceAdded(deviceAddress, deviceName, deviceIcon);
+ }
+ }
+
+ /**
+ * Dispatch a change in the about-to-connect device. See
+ * {@link DeviceCallback#onAboutToConnectDeviceRemoved} for more information.
+ */
+ public void dispatchAboutToConnectDeviceRemoved() {
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onAboutToConnectDeviceRemoved();
}
}
@@ -705,13 +717,27 @@
* connect imminently and should be displayed as the current device in the media player.
* See [AudioManager.muteAwaitConnection] for more details.
*
- * @param deviceName the name of the device (displayed to the user).
- * @param deviceIcon the icon that should be used with the device.
+ * The information in the most recent callback should override information from any previous
+ * callbacks.
+ *
+ * @param deviceAddress the address of the device. {@see AudioDeviceAttributes.address}.
+ * If present, we'll use this address to fetch the full information
+ * about the device (if we can find that information).
+ * @param deviceName the name of the device (displayed to the user). Used as a backup in
+ * case using deviceAddress doesn't work.
+ * @param deviceIcon the icon that should be used with the device. Used as a backup in case
+ * using deviceAddress doesn't work.
*/
- default void onAboutToConnectDeviceChanged(
- @Nullable String deviceName,
+ default void onAboutToConnectDeviceAdded(
+ @NonNull String deviceAddress,
+ @NonNull String deviceName,
@Nullable Drawable deviceIcon
) {}
+
+ /**
+ * Callback for notifying that we no longer have an about-to-connect device.
+ */
+ default void onAboutToConnectDeviceRemoved() {}
}
/**
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a10ca9e..a28c4cf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -575,6 +575,9 @@
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.TRUST_LISTENER" />
+ <!-- Permission required for CTS test - CtsTaskFpsCallbackTestCases -->
+ <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" />
+
<!-- Permission required for CTS test - CtsGameManagerTestCases -->
<uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 9d801d2..9ffafbc 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -16,8 +16,9 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="@dimen/qs_security_footer_single_line_height"
+ android:layout_weight="1"
android:gravity="center"
android:clickable="true"
android:visibility="gone">
@@ -26,7 +27,7 @@
android:id="@+id/fgs_text_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset"
android:background="@drawable/qs_security_footer_background"
android:layout_gravity="end"
android:gravity="center"
@@ -86,7 +87,7 @@
android:layout_height="12dp"
android:scaleType="fitCenter"
android:layout_gravity="bottom|end"
- android:src="@drawable/new_fgs_dot"
+ android:src="@drawable/fgs_dot"
android:contentDescription="@string/fgs_dot_content_description"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index fb401ee..6a1d62d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-** Copyright 2021, The Android Open Source Project
+** Copyright 2022, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -18,65 +18,80 @@
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
<com.android.systemui.qs.FooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
+ android:layout_height="@dimen/footer_actions_height"
+ android:elevation="@dimen/qs_panel_elevation"
+ android:paddingTop="8dp"
+ android:paddingBottom="4dp"
+ android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical"
android:layout_gravity="bottom"
>
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="0dp"
+ <LinearLayout
+ android:id="@+id/security_footers_container"
+ android:orientation="horizontal"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?android:attr/textColorPrimary" />
+ />
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_chip_background"
- android:layout_weight="1"
- android:clipChildren="false"
- android:clipToPadding="false">
+ <!-- Negative margin equal to -->
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
+ >
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="match_parent"
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:background="@drawable/qs_footer_action_chip_background_borderless"
+ android:background="@drawable/qs_footer_action_circle"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_gravity="center"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/textColorPrimary" />
+
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle_color"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?androidprv:attr/textColorOnAccent" />
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
+ </LinearLayout>
</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
deleted file mode 100644
index 59712c0..0000000
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2022, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-
-<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<com.android.systemui.qs.FooterActionsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/new_footer_height"
- android:elevation="@dimen/qs_panel_elevation"
- android:paddingTop="8dp"
- android:paddingBottom="4dp"
- android:background="@drawable/qs_footer_actions_background"
- android:gravity="center_vertical"
- android:layout_gravity="bottom"
->
-
- <LinearLayout
- android:id="@+id/security_footers_container"
- android:orientation="horizontal"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_width="0dp"
- android:layout_weight="1"
- />
-
- <!-- Negative margin equal to -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_marginEnd="@dimen/new_qs_footer_action_inset_negative"
- >
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:background="@android:color/transparent"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle_color"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?androidprv:attr/textColorOnAccent" />
-
- </LinearLayout>
-</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/new_fgs_dot.xml
rename to packages/SystemUI/res/drawable/fgs_dot.xml
diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_container.xml
new file mode 100644
index 0000000..79d2a06
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_connecting_container.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="48dp"
+ android:width="48dp"
+ android:viewportHeight="48"
+ android:viewportWidth="48">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5"/>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffddb3"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "/>
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
deleted file mode 100644
index 9076da7..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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"
- android:insetTop="@dimen/qs_footer_action_inset"
- android:insetBottom="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight"
- android:height="44dp">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?attr/underSurfaceColor"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <stroke android:width="1dp" android:color="?android:attr/colorBackground"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
deleted file mode 100644
index bbcfb15..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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"
- android:insetTop="@dimen/qs_footer_action_inset"
- android:insetBottom="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index 31a8c3b..c8c36b0 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/new_qs_footer_action_inset">
+ android:inset="@dimen/qs_footer_action_inset">
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 021a85f..6a365000 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/new_qs_footer_action_inset">
+ android:inset="@dimen/qs_footer_action_inset">
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b6e3499..b1d3ed05 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -29,11 +29,6 @@
android:clipChildren="false"
android:clipToPadding="false">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_height"
@@ -80,14 +75,4 @@
</LinearLayout>
- <ViewStub
- android:id="@+id/footer_stub"
- android:inflatedId="@+id/qs_footer_actions"
- android:layout="@layout/footer_actions"
- android:layout_height="@dimen/qs_footer_height"
- android:layout_width="match_parent"
- />
-
- </LinearLayout>
-
</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 2040051..1eb05bf 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,11 +47,10 @@
<include layout="@layout/quick_status_bar_expanded_header" />
- <ViewStub
- android:id="@+id/container_stub"
- android:inflatedId="@+id/qs_footer_actions"
- android:layout="@layout/new_footer_actions"
- android:layout_height="@dimen/new_footer_height"
+ <include
+ layout="@layout/footer_actions"
+ android:id="@+id/qs_footer_actions"
+ android:layout_height="@dimen/footer_actions_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
/>
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
index 08bd71c..1b11816 100644
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
@@ -14,19 +14,18 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.util.DualHeightHorizontalLinearLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_security_footer_height"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_security_footer_single_line_height"
+ android:layout_weight="1"
android:clickable="true"
- android:padding="@dimen/qs_footer_padding"
+ android:orientation="horizontal"
+ android:paddingHorizontal="@dimen/qs_footer_padding"
android:gravity="center_vertical"
android:layout_gravity="center_vertical|center_horizontal"
- android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset"
android:background="@drawable/qs_security_footer_background"
- systemui:singleLineHeight="@dimen/qs_security_footer_single_line_height"
- systemui:textViewId="@id/footer_text"
>
<ImageView
@@ -43,7 +42,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:maxLines="@integer/qs_security_footer_maxLines"
+ android:singleLine="true"
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
android:textColor="?android:attr/textColorSecondary"/>
@@ -58,4 +57,4 @@
android:autoMirrored="true"
android:tint="?android:attr/textColorSecondary" />
-</com.android.systemui.util.DualHeightHorizontalLinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index b1e8c38..60bc373 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -52,7 +52,6 @@
<!-- We want this to be centered (to align with notches). In order to do that, the following
has to hold (in portrait):
* date_container and privacy_container must have the same width and weight
- * header_text_container must be gone
-->
<android.widget.Space
android:id="@+id/space"
@@ -61,17 +60,6 @@
android:layout_gravity="center_vertical|center_horizontal"
android:visibility="gone" />
- <!-- Will hold security footer in landscape with media -->
- <FrameLayout
- android:id="@+id/header_text_container"
- android:layout_height="match_parent"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:gravity="center"
- />
-
<FrameLayout
android:id="@+id/privacy_container"
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8de8084..c60609b 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -98,6 +98,7 @@
android:scaleType="fitEnd"
android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
+ android:clickable="true"
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 62903d5..3228c3c 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -188,12 +188,6 @@
<attr name="borderColor" format="color" />
</declare-styleable>
- <declare-styleable name="DualHeightHorizontalLinearLayout">
- <attr name="singleLineHeight" format="dimension" />
- <attr name="singleLineVerticalPadding" format="dimension" />
- <attr name="textViewId" format="reference" />
- </declare-styleable>
-
<attr name="overlayButtonTextColor" format="color" />
<declare-styleable name="DreamOverlayDotImageView">
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 50fdc7b..24118d2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -137,9 +137,11 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#7DA7F1</color>
<color name="udfps_moving_target_fill">#475670</color>
+ <!-- 50% of udfps_moving_target_fill-->
+ <color name="udfps_moving_target_fill_error">#80475670</color>
<color name="udfps_enroll_progress">#7DA7F1</color>
<color name="udfps_enroll_progress_help">#607DA7F1</color>
- <color name="udfps_enroll_progress_help_with_talkback">#ffEE675C</color>
+ <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
<!-- Floating overlay actions -->
<color name="overlay_button_ripple">#1f000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 851c6dd..7672251 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -327,23 +327,20 @@
car setting. -->
<dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
- <!-- The height of the quick settings footer that holds the user switcher, settings icon,
- etc. -->
+ <!-- The height of the quick settings footer that holds the pagination dots and edit button -->
<dimen name="qs_footer_height">48dp</dimen>
<!-- 40dp (circles) + 8dp (circle padding) + 8dp (top) + 4dp (bottom) -->
- <dimen name="new_footer_height">60dp</dimen>
+ <dimen name="footer_actions_height">60dp</dimen>
<!-- The size of each of the icon buttons in the QS footer -->
<dimen name="qs_footer_action_button_size">48dp</dimen>
<dimen name="qs_footer_action_corner_radius">20dp</dimen>
- <!-- (48dp - 44dp) / 2 -->
- <dimen name="qs_footer_action_inset">2dp</dimen>
<!-- (48dp - 40dp) / 2 -->
- <dimen name="new_qs_footer_action_inset">4dp</dimen>
- <dimen name="new_qs_footer_action_inset_negative">-4dp</dimen>
+ <dimen name="qs_footer_action_inset">4dp</dimen>
+ <dimen name="qs_footer_action_inset_negative">-4dp</dimen>
<!-- Margins on each side of QS Footer -->
<dimen name="qs_footer_margin">2dp</dimen>
@@ -495,7 +492,7 @@
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
- <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
+ <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
<dimen name="qs_panel_padding_top">48dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 926734c..ff71b4f 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -23,6 +23,7 @@
<item type="id" name="scale_x_animator_tag"/>
<item type="id" name="scale_y_animator_tag"/>
<item type="id" name="top_inset_animator_tag"/>
+ <item type="id" name="bottom_inset_animator_tag"/>
<item type="id" name="height_animator_tag"/>
<item type="id" name="x_animator_tag"/>
<item type="id" name="y_animator_tag"/>
@@ -33,6 +34,7 @@
<item type="id" name="scale_y_animator_end_value_tag"/>
<item type="id" name="alpha_animator_end_value_tag"/>
<item type="id" name="top_inset_animator_end_value_tag"/>
+ <item type="id" name="bottom_inset_animator_end_value_tag"/>
<item type="id" name="height_animator_end_value_tag"/>
<item type="id" name="x_animator_tag_end_value"/>
<item type="id" name="y_animator_tag_end_value"/>
@@ -43,6 +45,7 @@
<item type="id" name="scale_y_animator_start_value_tag"/>
<item type="id" name="alpha_animator_start_value_tag"/>
<item type="id" name="top_inset_animator_start_value_tag"/>
+ <item type="id" name="bottom_inset_animator_start_value_tag"/>
<item type="id" name="height_animator_start_value_tag"/>
<item type="id" name="x_animator_tag_start_value"/>
<item type="id" name="y_animator_tag_start_value"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 33ed7b8..b248efe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2199,6 +2199,8 @@
<string name="controls_media_button_prev">Previous track</string>
<!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
<string name="controls_media_button_next">Next track</string>
+ <!-- Description for button in media controls. Used when media is connecting to a remote device (via something like chromecast). Pressing button does nothing [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_connecting">Connecting</string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 6541206..5953611 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -54,6 +54,7 @@
val bouncerFullyShown: Boolean,
val faceAuthenticated: Boolean,
val faceDisabled: Boolean,
+ val goingToSleep: Boolean,
val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index dd2664d..1383635 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -628,7 +628,8 @@
*/
public void setKeyguardGoingAway(boolean goingAway) {
mKeyguardGoingAway = goingAway;
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ // This is set specifically to stop face authentication from running.
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
}
/**
@@ -1746,7 +1747,8 @@
cb.onFinishedGoingToSleep(arg1);
}
}
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ // This is set specifically to stop face authentication from running.
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
}
private void handleScreenTurnedOff() {
@@ -1764,7 +1766,12 @@
cb.onDreamingStateChanged(mIsDreaming);
}
}
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mIsDreaming) {
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+ } else {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ }
}
private void handleUserInfoChanged(int userId) {
@@ -2477,9 +2484,11 @@
boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
&& !mBouncerFullyShown);
- // If the device supports face detection (without authentication), allow it to happen
- // if the device is in lockdown mode. Otherwise, prevent scanning.
+ // If the device supports face detection (without authentication) and bypass is enabled,
+ // allow face scanning to happen if the device is in lockdown mode.
+ // Otherwise, prevent scanning.
final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
+ && canBypass
&& mFaceSensorProperties.get(0).supportsFaceDetection;
if (isLockDown && !supportsDetectOnly) {
strongAuthAllowsScanning = false;
@@ -2494,8 +2503,11 @@
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
- (mBouncerFullyShown || mAuthInterruptActive || mOccludingAppRequestingFace
- || awakeKeyguard || shouldListenForFaceAssistant
+ (mBouncerFullyShown && !mGoingToSleep
+ || mAuthInterruptActive
+ || mOccludingAppRequestingFace
+ || awakeKeyguard
+ || shouldListenForFaceAssistant
|| mAuthController.isUdfpsFingerDown())
&& !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
&& !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed
@@ -2517,6 +2529,7 @@
mBouncerFullyShown,
faceAuthenticated,
faceDisabledForUser,
+ mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index f3a603f..59c658f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -23,6 +23,7 @@
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
@@ -40,13 +41,15 @@
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
- private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
private static final float STROKE_WIDTH_DP = 12f;
+ private static final Interpolator DEACCEL = new DecelerateInterpolator();
private final float mStrokeWidthPx;
@ColorInt private final int mProgressColor;
@ColorInt private final int mHelpColor;
+ @ColorInt private final int mOnFirstBucketFailedColor;
@NonNull private final Drawable mCheckmarkDrawable;
@NonNull private final Interpolator mCheckmarkInterpolator;
@NonNull private final Paint mBackgroundPaint;
@@ -64,6 +67,9 @@
@Nullable private ValueAnimator mFillColorAnimator;
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+ @Nullable private ValueAnimator mBackgroundColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
+
private boolean mComplete = false;
private float mCheckmarkScale = 0f;
@Nullable private ValueAnimator mCheckmarkAnimator;
@@ -76,8 +82,10 @@
final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
if (!isAccessbilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+ mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -112,6 +120,11 @@
mCheckmarkScale = (float) animation.getAnimatedValue();
invalidateSelf();
};
+
+ mBackgroundColorUpdateListener = animation -> {
+ mBackgroundPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
}
void onEnrollmentProgress(int remaining, int totalSteps) {
@@ -163,19 +176,38 @@
}
}
+ private void animateBackgroundColor() {
+ if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
+ mBackgroundColorAnimator.end();
+ }
+ mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
+ mOnFirstBucketFailedColor);
+ mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mBackgroundColorAnimator.setRepeatCount(1);
+ mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mBackgroundColorAnimator.setInterpolator(DEACCEL);
+ mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
+ mBackgroundColorAnimator.start();
+ }
+
private void updateFillColor(boolean showingHelp) {
- if (mShowingHelp == showingHelp) {
+ if (!mAfterFirstTouch && showingHelp) {
+ // If we are on the first touch, animate the background color
+ // instead of the progress color.
+ animateBackgroundColor();
return;
}
- mShowingHelp = showingHelp;
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
- mFillColorAnimator.cancel();
+ mFillColorAnimator.end();
}
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.setRepeatCount(1);
+ mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mFillColorAnimator.setInterpolator(DEACCEL);
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
mFillColorAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 9356b16..c9a61a8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -112,6 +112,10 @@
public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+ /**
+ * @deprecated Not needed anymore
+ */
+ @Deprecated
public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b93239b..d674b2b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -690,6 +690,11 @@
startDelay: Long = 0
) {
desiredHostState?.let {
+ if (this.desiredLocation != desiredLocation) {
+ // Only log an event when location changes
+ logger.logCarouselPosition(desiredLocation)
+ }
+
// This is a hosting view, let's remeasure our players
this.desiredLocation = desiredLocation
this.desiredHostState = it
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5ead375..05b2c50 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -333,7 +333,7 @@
mMediaViewHolder.getPlayer().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
if (mMediaViewController.isGutsVisible()) return;
-
+ mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
@@ -660,38 +660,43 @@
final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
ConstraintSet expandedSet, boolean showInCompact) {
- animHandler.unregisterAll();
if (mediaAction != null) {
- final Drawable icon = mediaAction.getIcon();
- button.setImageDrawable(icon);
- button.setContentDescription(mediaAction.getContentDescription());
- final Drawable bgDrawable = mediaAction.getBackground();
- button.setBackground(bgDrawable);
+ if (animHandler.updateRebindId(mediaAction.getRebindId())) {
+ animHandler.unregisterAll();
- animHandler.tryRegister(icon);
- animHandler.tryRegister(bgDrawable);
+ final Drawable icon = mediaAction.getIcon();
+ button.setImageDrawable(icon);
+ button.setContentDescription(mediaAction.getContentDescription());
+ final Drawable bgDrawable = mediaAction.getBackground();
+ button.setBackground(bgDrawable);
- Runnable action = mediaAction.getAction();
- if (action == null) {
- button.setEnabled(false);
- } else {
- button.setEnabled(true);
- button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
- logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
- action.run();
+ animHandler.tryRegister(icon);
+ animHandler.tryRegister(bgDrawable);
- if (icon instanceof Animatable) {
- ((Animatable) icon).start();
+ Runnable action = mediaAction.getAction();
+ if (action == null) {
+ button.setEnabled(false);
+ } else {
+ button.setEnabled(true);
+ button.setOnClickListener(v -> {
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
+ mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
+ logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
+ action.run();
+
+ if (icon instanceof Animatable) {
+ ((Animatable) icon).start();
+ }
+ if (bgDrawable instanceof Animatable) {
+ ((Animatable) bgDrawable).start();
+ }
}
- if (bgDrawable instanceof Animatable) {
- ((Animatable) bgDrawable).start();
- }
- }
- });
+ });
+ }
}
} else {
+ animHandler.unregisterAll();
button.setImageDrawable(null);
button.setContentDescription(null);
button.setEnabled(false);
@@ -702,9 +707,29 @@
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
}
+ // AnimationBindHandler is responsible for tracking the bound animation state and preventing
+ // jank and conflicts due to media notifications arriving at any time during an animation. It
+ // does this in two parts.
+ // - Exit animations fired as a result of user input are tracked. When these are running, any
+ // bind actions are delayed until the animation completes (and then fired in sequence).
+ // - Continuous animations are tracked using their rebind id. Later calls using the same
+ // rebind id will be totally ignored to prevent the continuous animation from restarting.
private static class AnimationBindHandler extends Animatable2.AnimationCallback {
private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
+ private Integer mRebindId = null;
+
+ // This check prevents rebinding to the action button if the identifier has not changed. A
+ // null value is always considered to be changed. This is used to prevent the connecting
+ // animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
+ // an application in a row.
+ public boolean updateRebindId(Integer rebindId) {
+ if (mRebindId == null || rebindId == null || !mRebindId.equals(rebindId)) {
+ mRebindId = rebindId;
+ return true;
+ }
+ return false;
+ }
public void tryRegister(Drawable drawable) {
if (drawable instanceof Animatable2) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index a4d2f7b..bc8cca5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -184,7 +184,12 @@
val icon: Drawable?,
val action: Runnable?,
val contentDescription: CharSequence?,
- val background: Drawable?
+ val background: Drawable?,
+
+ // Rebind Id is used to detect identical rebinds and ignore them. It is intended
+ // to prevent continuously looping animations from restarting due to the arrival
+ // of repeated media notifications that are visually identical.
+ val rebindId: Int? = null
)
/** State of the media device. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 908aef4..57c93ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
+import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -57,6 +58,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
@@ -777,7 +779,20 @@
val actions = MediaButton()
controller.playbackState?.let { state ->
// First, check for standard actions
- actions.playOrPause = if (isPlayingState(state.state)) {
+ actions.playOrPause = if (isConnectingState(state.state)) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable = context.getDrawable(
+ com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
} else {
getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 824a6fd..d623191 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -164,7 +164,7 @@
}
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
- private var aboutToConnectDeviceOverride: MediaDeviceData? = null
+ private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@AnyThread
fun start() = bgExecutor.execute {
@@ -222,22 +222,34 @@
}
}
- override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
- aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
- null
- } else {
- MediaDeviceData(enabled = true, deviceIcon, deviceName)
- }
+ override fun onAboutToConnectDeviceAdded(
+ deviceAddress: String,
+ deviceName: String,
+ deviceIcon: Drawable?
+ ) {
+ aboutToConnectDeviceOverride = AboutToConnectDevice(
+ fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
+ backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
+ )
+ updateCurrent()
+ }
+
+ override fun onAboutToConnectDeviceRemoved() {
+ aboutToConnectDeviceOverride = null
updateCurrent()
}
@WorkerThread
private fun updateCurrent() {
- if (aboutToConnectDeviceOverride != null) {
- current = aboutToConnectDeviceOverride
- return
+ val aboutToConnect = aboutToConnectDeviceOverride
+ if (aboutToConnect != null &&
+ aboutToConnect.fullMediaDevice == null &&
+ aboutToConnect.backupMediaDeviceData != null) {
+ // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
+ current = aboutToConnect.backupMediaDeviceData
+ return
}
- val device = localMediaManager.currentConnectedDevice
+ val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
// If we have a controller but get a null route, then don't trust the device
@@ -247,3 +259,17 @@
}
}
}
+
+/**
+ * A class storing information for the about-to-connect device. See
+ * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
+ *
+ * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
+ * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
+ * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum
+ * information required to display the device. Only use if [fullMediaDevice] is null.
+ */
+private data class AboutToConnectDevice(
+ val fullMediaDevice: MediaDevice? = null,
+ val backupMediaDeviceData: MediaDeviceData? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3d9f933..30771c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -1146,7 +1146,10 @@
@Retention(AnnotationRetention.SOURCE)
private annotation class TransformationType
-@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
- MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
+@IntDef(prefix = ["LOCATION_"], value = [
+ MediaHierarchyManager.LOCATION_QS,
+ MediaHierarchyManager.LOCATION_QQS,
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY])
@Retention(AnnotationRetention.SOURCE)
annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
index 862b279..3eba3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -134,6 +134,23 @@
fun logOpenOutputSwitcher(uid: Int, packageName: String, instanceId: InstanceId) {
logger.logWithInstanceId(MediaUiEvent.OPEN_OUTPUT_SWITCHER, uid, packageName, instanceId)
}
+
+ fun logTapContentView(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_TAP_CONTENT_VIEW, uid, packageName, instanceId)
+ }
+
+ fun logCarouselPosition(@MediaLocation location: Int) {
+ val event = when (location) {
+ MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
+ MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
+ MediaHierarchyManager.LOCATION_LOCKSCREEN ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+ else -> throw IllegalArgumentException("Unknown media carousel location $location")
+ }
+ logger.log(event)
+ }
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -161,7 +178,7 @@
@UiEvent(doc = "An existing active media control was converted into resumable media")
ACTIVE_TO_RESUME(1014),
- @UiEvent(doc = "Media timed out")
+ @UiEvent(doc = "A media control timed out")
MEDIA_TIMEOUT(1015),
@UiEvent(doc = "A media control was removed from the carousel")
@@ -173,35 +190,50 @@
@UiEvent(doc = "The user swiped away the media carousel")
DISMISS_SWIPE(1018),
- @UiEvent(doc = "The user opened the long press menu")
+ @UiEvent(doc = "The user long pressed on a media control")
OPEN_LONG_PRESS(1019),
- @UiEvent(doc = "The user dismissed via long press menu")
+ @UiEvent(doc = "The user dismissed a media control via its long press menu")
DISMISS_LONG_PRESS(1020),
- @UiEvent(doc = "The user opened settings from long press menu")
+ @UiEvent(doc = "The user opened media settings from a media control's long press menu")
OPEN_SETTINGS_LONG_PRESS(1021),
- @UiEvent(doc = "The user opened settings from the carousel")
+ @UiEvent(doc = "The user opened media settings from the media carousel")
OPEN_SETTINGS_CAROUSEL(1022),
- @UiEvent(doc = "The play/pause button was tapped")
+ @UiEvent(doc = "The play/pause button on a media control was tapped")
TAP_ACTION_PLAY_PAUSE(1023),
- @UiEvent(doc = "The previous button was tapped")
+ @UiEvent(doc = "The previous button on a media control was tapped")
TAP_ACTION_PREV(1024),
- @UiEvent(doc = "The next button was tapped")
+ @UiEvent(doc = "The next button on a media control was tapped")
TAP_ACTION_NEXT(1025),
- @UiEvent(doc = "A custom or generic action button was tapped")
+ @UiEvent(doc = "A custom or generic action button on a media control was tapped")
TAP_ACTION_OTHER(1026),
- @UiEvent(doc = "The user seeked using the seekbar")
+ @UiEvent(doc = "The user seeked on a media control using the seekbar")
ACTION_SEEK(1027),
@UiEvent(doc = "The user opened the output switcher from a media control")
- OPEN_OUTPUT_SWITCHER(1028);
+ OPEN_OUTPUT_SWITCHER(1028),
+
+ @UiEvent(doc = "The user tapped on a media control view")
+ MEDIA_TAP_CONTENT_VIEW(1036),
+
+ @UiEvent(doc = "The media carousel moved to QQS")
+ MEDIA_CAROUSEL_LOCATION_QQS(1037),
+
+ @UiEvent(doc = "THe media carousel moved to QS")
+ MEDIA_CAROUSEL_LOCATION_QS(1038),
+
+ @UiEvent(doc = "The media carousel moved to the lockscreen")
+ MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
+
+ @UiEvent(doc = "The media carousel moved to the dream state")
+ MEDIA_CAROUSEL_LOCATION_DREAM(1040);
override fun getId() = metricId
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
index 22bc557..2783532 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
@@ -52,7 +52,9 @@
// There should only be one device that's mutedUntilConnection at a time, so we can
// safely override any previous value.
currentMutedDevice = device
- localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
+ localMediaManager.dispatchAboutToConnectDeviceAdded(
+ device.address, device.name, device.getIcon()
+ )
}
}
@@ -63,7 +65,7 @@
) {
if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
currentMutedDevice = null
- localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
+ localMediaManager.dispatchAboutToConnectDeviceRemoved()
}
}
}
@@ -76,8 +78,8 @@
val currentDevice = audioManager.mutingExpectedDevice
if (currentDevice != null) {
currentMutedDevice = currentDevice
- localMediaManager.dispatchAboutToConnectDeviceChanged(
- currentDevice.name, currentDevice.getIcon()
+ localMediaManager.dispatchAboutToConnectDeviceAdded(
+ currentDevice.address, currentDevice.name, currentDevice.getIcon()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 0365241..e5dc0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -360,9 +360,7 @@
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
mSwipeProgressThreshold = context.getResources()
.getDimension(R.dimen.navigation_edge_action_progress_threshold);
- if (mBackAnimation != null) {
- mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
- }
+ initializeBackAnimation();
setVisibility(GONE);
Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
@@ -391,6 +389,13 @@
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
+ initializeBackAnimation();
+ }
+
+ private void initializeBackAnimation() {
+ if (mBackAnimation != null) {
+ mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index c01d6dc..c6c9aca 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -466,7 +466,7 @@
}
}
} catch (SQLException e) {
- Log.e(TAG, "Failed to query contact: " + e);
+ Log.e(TAG, "Failed to query contact", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -527,7 +527,7 @@
lookupKeysWithBirthdaysToday.add(lookupKey);
}
} catch (SQLException e) {
- Log.e(TAG, "Failed to query birthdays: " + e);
+ Log.e(TAG, "Failed to query birthdays", e);
} finally {
if (cursor != null) {
cursor.close();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index f6e1cd4..1a7bd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -275,7 +275,7 @@
updateSingleConversationWidgets(widgetIds);
}
} catch (Exception e) {
- Log.e(TAG, "Exception: " + e);
+ Log.e(TAG, "failed to update widgets", e);
}
}
@@ -348,7 +348,7 @@
try {
return getTileForExistingWidgetThrowing(appWidgetId);
} catch (Exception e) {
- Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
+ Log.e(TAG, "failed to retrieve tile for widget ID " + appWidgetId, e);
return null;
}
}
@@ -423,7 +423,7 @@
// Add current state.
return getTileWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
} catch (RemoteException e) {
- Log.e(TAG, "Could not retrieve data: " + e);
+ Log.e(TAG, "getTileFromPersistentStorage failing", e);
return null;
}
}
@@ -441,12 +441,16 @@
Log.d(TAG, "Notification removed, key: " + sbn.getKey());
}
}
+ if (DEBUG) Log.d(TAG, "Fetching notifications");
+ Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
mBgExecutor.execute(
- () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
+ () -> updateWidgetsWithNotificationChangedInBackground(
+ sbn, notificationAction, notifications));
}
private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
- PeopleSpaceUtils.NotificationAction action) {
+ PeopleSpaceUtils.NotificationAction action,
+ Collection<NotificationEntry> notifications) {
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -469,23 +473,23 @@
Log.d(TAG, "Widgets by URI to be updated:" + tilesUpdatedByUri.toString());
}
tilesUpdated.addAll(tilesUpdatedByUri);
- updateWidgetIdsBasedOnNotifications(tilesUpdated);
+ updateWidgetIdsBasedOnNotifications(tilesUpdated, notifications);
}
} catch (Exception e) {
- Log.e(TAG, "Throwing exception: " + e);
+ Log.e(TAG, "updateWidgetsWithNotificationChangedInBackground failing", e);
}
}
/** Updates {@code widgetIdsToUpdate} with {@code action}. */
- private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate) {
+ private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate,
+ Collection<NotificationEntry> ungroupedNotifications) {
if (widgetIdsToUpdate.isEmpty()) {
if (DEBUG) Log.d(TAG, "No widgets to update, returning.");
return;
}
try {
- if (DEBUG) Log.d(TAG, "Fetching grouped notifications");
Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
- getGroupedConversationNotifications();
+ groupConversationNotifications(ungroupedNotifications);
widgetIdsToUpdate
.stream()
@@ -495,7 +499,7 @@
id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
.forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
} catch (Exception e) {
- Log.e(TAG, "Exception updating widgets: " + e);
+ Log.e(TAG, "updateWidgetIdsBasedOnNotifications failing", e);
}
}
@@ -510,7 +514,7 @@
"Augmenting tile from NotificationEntryManager widget: " + key.toString());
}
Map<PeopleTileKey, Set<NotificationEntry>> notifications =
- getGroupedConversationNotifications();
+ groupConversationNotifications(mNotifCollection.getAllNotifs());
String contactUri = null;
if (tile.getContactUri() != null) {
contactUri = tile.getContactUri().toString();
@@ -518,9 +522,10 @@
return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
}
- /** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
- public Map<PeopleTileKey, Set<NotificationEntry>> getGroupedConversationNotifications() {
- Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
+ /** Groups active and pending notifications grouped by {@link PeopleTileKey}. */
+ public Map<PeopleTileKey, Set<NotificationEntry>> groupConversationNotifications(
+ Collection<NotificationEntry> notifications
+ ) {
if (DEBUG) Log.d(TAG, "Number of total notifications: " + notifications.size());
Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
notifications
@@ -846,7 +851,7 @@
Collections.singletonList(tile.getId()),
tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
- Log.w(TAG, "Exception caching shortcut:" + e);
+ Log.w(TAG, "failed to cache shortcut", e);
}
PeopleSpaceTile finalTile = tile;
mBgExecutor.execute(
@@ -954,7 +959,7 @@
UserHandle.of(key.getUserId()),
LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
- Log.d(TAG, "Exception uncaching shortcut:" + e);
+ Log.d(TAG, "failed to uncache shortcut", e);
}
}
@@ -1037,7 +1042,7 @@
packageName, userHandle.getIdentifier(), shortcutId);
tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
} catch (Exception e) {
- Log.w(TAG, "Exception getting tiles: " + e);
+ Log.w(TAG, "failed to get conversation or tile", e);
return null;
}
if (tile == null) {
@@ -1086,7 +1091,7 @@
}
} catch (PackageManager.NameNotFoundException e) {
// Delete data for uninstalled widgets.
- Log.e(TAG, "Package no longer found for tile: " + e);
+ Log.e(TAG, "package no longer found for tile", e);
JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
if (jobScheduler != null
&& jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
@@ -1296,7 +1301,7 @@
try {
editor.putString(newId, (String) entry.getValue());
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
}
editor.remove(key);
break;
@@ -1306,7 +1311,7 @@
try {
oldWidgetIds = (Set<String>) entry.getValue();
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
editor.remove(key);
break;
}
@@ -1337,7 +1342,7 @@
try {
oldWidgetIds = (Set<String>) entry.getValue();
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
followUpEditor.remove(key);
continue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 44ef2b6..3f394e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -23,7 +23,6 @@
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import android.view.View
import android.view.ViewGroup
-import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
@@ -32,8 +31,6 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -45,7 +42,6 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import com.android.systemui.util.DualHeightHorizontalLinearLayout
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -74,8 +70,7 @@
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
private val globalSetting: GlobalSettings,
- private val handler: Handler,
- private val featureFlags: FeatureFlags
+ private val handler: Handler
) : ViewController<FooterActionsView>(view) {
private var globalActionsDialog: GlobalActionsDialogLite? = null
@@ -100,7 +95,9 @@
view.findViewById(R.id.security_footers_container)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
- private val securityFootersSeparator = View(context).apply {
+
+ @VisibleForTesting
+ internal val securityFootersSeparator = View(context).apply {
visibility = View.GONE
}
@@ -171,48 +168,30 @@
}
settingsButton.setOnClickListener(onClickListener)
multiUserSetting.isListening = true
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
- securityFootersContainer?.addView(securityFooter)
- val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
- securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
- reformatForNewFooter(securityFooter)
- val fgsFooter = fgsManagerFooterController.view
- securityFootersContainer?.addView(fgsFooter)
- (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply {
- width = 0
- weight = 1f
- }
- val visibilityListener =
- VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
- if (visibility == View.GONE) {
- securityFootersSeparator.visibility = View.GONE
- } else if (securityFooter.visibility == View.VISIBLE &&
- fgsFooter.visibility == View.VISIBLE) {
- securityFootersSeparator.visibility = View.VISIBLE
- } else {
- securityFootersSeparator.visibility = View.GONE
- }
- fgsManagerFooterController
- .setCollapsed(securityFooter.visibility == View.VISIBLE)
+ val securityFooter = securityFooterController.view
+ securityFootersContainer?.addView(securityFooter)
+ val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
+ securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
+
+ val fgsFooter = fgsManagerFooterController.view
+ securityFootersContainer?.addView(fgsFooter)
+
+ val visibilityListener =
+ VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
+ if (securityFooter.visibility == View.VISIBLE &&
+ fgsFooter.visibility == View.VISIBLE) {
+ securityFootersSeparator.visibility = View.VISIBLE
+ } else {
+ securityFootersSeparator.visibility = View.GONE
}
- securityFooterController.setOnVisibilityChangedListener(visibilityListener)
- fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
- }
- updateView()
- }
+ fgsManagerFooterController
+ .setCollapsed(securityFooter.visibility == View.VISIBLE)
+ }
+ securityFooterController.setOnVisibilityChangedListener(visibilityListener)
+ fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
- private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) {
- // This is only necessary while things are flagged as the view could be attached in two
- // different locations.
- (view.layoutParams as LinearLayout.LayoutParams).apply {
- bottomMargin = 0
- width = 0
- weight = 1f
- marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
- }
- view.alwaysSingleLine = true
+ updateView()
}
private fun updateView() {
@@ -237,10 +216,9 @@
} else {
userInfoController.removeCallback(onUserInfoChangedListener)
}
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- fgsManagerFooterController.setListening(listening)
- securityFooterController.setListening(listening)
- }
+
+ fgsManagerFooterController.setListening(listening)
+ securityFooterController.setListening(listening)
}
fun disable(state2: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 4640205..56298fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -81,8 +81,6 @@
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
private final QuickStatusBarHeader mQuickStatusBarHeader;
- private final QSFgsManagerFooter mFgsManagerFooter;
- private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
@Nullable
@@ -105,7 +103,7 @@
private TouchAnimator mNonfirstPageAlphaAnimator;
// TranslatesY the QS Tile layout using QS.getHeightDiff()
private TouchAnimator mQSTileLayoutTranslatorAnimator;
- // This animates fading of SecurityFooter and media divider
+ // This animates fading of media player
private TouchAnimator mAllPagesDelayedAnimator;
// Animator for brightness slider(s)
@Nullable
@@ -146,7 +144,6 @@
public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
- QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
@Main Executor executor, TunerService tunerService,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
@@ -154,8 +151,6 @@
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mQuickStatusBarHeader = quickStatusBarHeader;
- mFgsManagerFooter = fgsManagerFooter;
- mSecurityFooter = securityFooter;
mHost = qsTileHost;
mExecutor = executor;
mTunerService = tunerService;
@@ -472,10 +467,8 @@
.setListener(this)
.build();
- // Fade in the security footer and the divider as we reach the final position
+ // Fade in the media player as we reach the final position
Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
- builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
- builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
if (mQsPanelController.shouldUseHorizontalLayout()
&& mQsPanelController.mMediaHost.hostView != null) {
builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
@@ -484,8 +477,6 @@
mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
}
mAllPagesDelayedAnimator = builder.build();
- mAllViews.add(mFgsManagerFooter.getView());
- mAllViews.add(mSecurityFooter.getView());
translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 519ed5c..6658441 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -138,12 +138,8 @@
}
void updateResources(QSPanelController qsPanelController,
- QuickStatusBarHeaderController quickStatusBarHeaderController,
- boolean newFooter) {
- int bottomPadding = 0;
- if (newFooter) {
- bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
- }
+ QuickStatusBarHeaderController quickStatusBarHeaderController) {
+ int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
mQSPanelContainer.setPaddingRelative(
mQSPanelContainer.getPaddingStart(),
QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 61da182..7d61991 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -18,8 +18,6 @@
import android.content.res.Configuration;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -32,26 +30,23 @@
private final QSPanelController mQsPanelController;
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
private final ConfigurationController mConfigurationController;
- private final boolean mNewFooter;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
- mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
}
};
@Inject
QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
- ConfigurationController configurationController,
- FeatureFlags featureFlags) {
+ ConfigurationController configurationController) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
- mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
}
@Override
@@ -65,7 +60,7 @@
@Override
protected void onViewAttached() {
- mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
mConfigurationController.addCallback(mConfigurationListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 795a606..4fa05c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -211,7 +211,6 @@
}
});
mHeader = view.findViewById(R.id.header);
- mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
mFooter = qsFragmentComponent.getQSFooter();
mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8c4dedc..9fbdd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -75,7 +75,6 @@
@Nullable
protected BrightnessSliderController mToggleSliderController;
- private final H mHandler = new H();
/** Whether or not the QS media player feature is enabled. */
protected boolean mUsingMediaPlayer;
@@ -87,16 +86,9 @@
new ArrayList<>();
@Nullable
- protected View mFgsManagerFooter;
- @Nullable
- protected View mSecurityFooter;
-
- @Nullable
protected View mFooter;
@Nullable
- private ViewGroup mHeaderContainer;
- @Nullable
private PageIndicator mFooterPageIndicator;
private int mContentMarginStart;
private int mContentMarginEnd;
@@ -112,7 +104,6 @@
private float mSquishinessFraction = 1f;
private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
private final Rect mClippingRect = new Rect();
- private boolean mUseNewFooter = false;
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
@@ -156,10 +147,6 @@
}
}
- void setUseNewFooter(boolean useNewFooter) {
- mUseNewFooter = useNewFooter;
- }
-
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -444,27 +431,6 @@
}
}
- /** Switch the security footer between top and bottom of QS depending on orientation. */
- public void switchSecurityFooter(boolean shouldUseSplitNotificationShade) {
- if (mSecurityFooter == null) return;
-
- if (!shouldUseSplitNotificationShade
- && mContext.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
- // Adding the security view to the header, that enables us to avoid scrolling
- switchToParent(mSecurityFooter, mHeaderContainer, 0);
- } else {
- // Add after the footer
- int index;
- if (mFgsManagerFooter != null) {
- index = indexOfChild(mFgsManagerFooter);
- } else {
- index = indexOfChild(mFooter);
- }
- switchToParent(mSecurityFooter, this, index + 1);
- }
- }
-
private void switchToParent(View child, ViewGroup parent, int index) {
switchToParent(child, parent, index, getDumpableTag());
}
@@ -609,38 +575,10 @@
}
}
- /**
- * Set the header container of quick settings.
- */
- public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
- mHeaderContainer = headerContainer;
- }
-
public boolean isListening() {
return mListening;
}
- /**
- * Set the security footer view and switch it into the right place
- * @param view the view in question
- * @param shouldUseSplitNotificationShade if QS is in split shade mode
- */
- public void setSecurityFooter(View view, boolean shouldUseSplitNotificationShade) {
- mSecurityFooter = view;
- switchSecurityFooter(shouldUseSplitNotificationShade);
- }
-
- /**
- * Set the fgs manager footer view and switch it into the right place
- * @param view the view in question
- */
- public void setFgsManagerFooter(View view) {
- mFgsManagerFooter = view;
- // Add after the footer
- int index = indexOfChild(mFooter);
- switchToParent(mFgsManagerFooter, this, index + 1);
- }
-
protected void setPageMargin(int pageMargin) {
if (mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b46f9c3..5670836 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -21,18 +21,14 @@
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
-import android.annotation.NonNull;
import android.content.res.Configuration;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -56,8 +52,6 @@
@QSScope
public class QSPanelController extends QSPanelControllerBase<QSPanel> {
- private final QSFgsManagerFooter mQSFgsManagerFooter;
- private final QSSecurityFooter mQsSecurityFooter;
private final TunerService mTunerService;
private final QSCustomizerController mQsCustomizerController;
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
@@ -65,7 +59,6 @@
private final BrightnessController mBrightnessController;
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
- private final FeatureFlags mFeatureFlags;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mGridContentVisible = true;
@@ -75,11 +68,9 @@
@Override
public void onConfigurationChange(Configuration newConfig) {
mView.updateResources();
- mQsSecurityFooter.onConfigurationChanged();
if (mView.isListening()) {
refreshAllTiles();
}
- mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
}
};
@@ -94,8 +85,7 @@
};
@Inject
- QSPanelController(QSPanel view, QSFgsManagerFooter qsFgsManagerFooter,
- QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+ QSPanelController(QSPanel view, TunerService tunerService,
QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
@@ -103,12 +93,10 @@
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
- FalsingManager falsingManager, FeatureFlags featureFlags,
+ FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
- mQSFgsManagerFooter = qsFgsManagerFooter;
- mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
@@ -119,9 +107,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
- mFeatureFlags = featureFlags;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER));
}
@Override
@@ -132,7 +118,6 @@
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
mQsCustomizerController.init();
mBrightnessSliderController.init();
- mQSFgsManagerFooter.init();
}
@Override
@@ -147,10 +132,6 @@
refreshAllTiles();
}
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
- if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
- mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
- mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
- }
switchTileLayout(true);
mBrightnessMirrorHandler.onQsPanelAttached();
@@ -172,13 +153,6 @@
super.onViewDetached();
}
- /**
- * Set the header container of quick settings.
- */
- public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
- mView.setHeaderContainer(headerContainer);
- }
-
/** */
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
@@ -191,11 +165,6 @@
refreshAllTiles();
}
- if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
- mQSFgsManagerFooter.setListening(listening);
- mQsSecurityFooter.setListening(listening);
- }
-
// Set the listening as soon as the QS fragment starts listening regardless of the
//expansion, so it will update the current brightness before the slider is visible.
if (listening) {
@@ -224,8 +193,6 @@
public void refreshAllTiles() {
mBrightnessController.checkRestrictionAndSetEnabled();
super.refreshAllTiles();
- mQSFgsManagerFooter.refreshState();
- mQsSecurityFooter.refreshState();
}
/** Start customizing the Quick Settings. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c5ca285..264edb1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -70,7 +70,6 @@
private View mDateView;
// DateView next to clock. Visible on QQS
private VariableDateView mClockDateView;
- private View mSecurityHeaderView;
private View mStatusIconsView;
private View mContainer;
@@ -137,7 +136,6 @@
mPrivacyChip = findViewById(R.id.privacy_chip);
mDateView = findViewById(R.id.date);
mClockDateView = findViewById(R.id.date_clock);
- mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
mDateContainer = findViewById(R.id.date_container);
@@ -152,8 +150,6 @@
updateResources();
Configuration config = mContext.getResources().getConfiguration();
setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
- setSecurityHeaderContainerVisibility(
- config.orientation == Configuration.ORIENTATION_LANDSCAPE);
// QS will always show the estimate, and BatteryMeterView handles the case where
// it's unavailable or charging
@@ -207,8 +203,6 @@
super.onConfigurationChanged(newConfig);
updateResources();
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
- setSecurityHeaderContainerVisibility(
- newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -229,10 +223,6 @@
mPrivacyContainer.setLayoutParams(lp);
}
- private void setSecurityHeaderContainerVisibility(boolean landscape) {
- mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
- }
-
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -337,7 +327,6 @@
return;
}
TouchAnimator.Builder builder = new TouchAnimator.Builder()
- .addFloat(mSecurityHeaderView, "alpha", 0, 1)
// These views appear on expanding down
.addFloat(mDateView, "alpha", 0, 0, 1)
.addFloat(mClockDateView, "alpha", 1, 0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 2780b16..aa505fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -22,13 +22,10 @@
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewStub;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.FooterActionsView;
@@ -128,15 +125,7 @@
* This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
*/
@Provides
- static FooterActionsView providesQSFooterActionsView(@RootView View view,
- FeatureFlags featureFlags) {
- ViewStub stub;
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- stub = view.requireViewById(R.id.container_stub);
- } else {
- stub = view.requireViewById(R.id.footer_stub);
- }
- stub.inflate();
+ static FooterActionsView providesQSFooterActionsView(@RootView View view) {
return view.findViewById(R.id.qs_footer_actions);
}
@@ -161,9 +150,10 @@
@Named(QS_SECURITY_FOOTER_VIEW)
static View providesQSSecurityFooterView(
@QSThemedContext LayoutInflater layoutInflater,
- QSPanel qsPanel
+ FooterActionsView footerActionsView
) {
- return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+ return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView,
+ false);
}
/** */
@@ -200,8 +190,8 @@
@Named(QS_FGS_MANAGER_FOOTER_VIEW)
static View providesQSFgsManagerFooterView(
@QSThemedContext LayoutInflater layoutInflater,
- QSPanel qsPanel
+ FooterActionsView footerActionsView
) {
- return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false);
+ return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 41e468d..50ee1f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -246,7 +246,9 @@
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
@@ -256,6 +258,7 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
// cancel current pending intent (if any) since clipData isn't used for matching
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
context, 0, sharingChooserIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7239d0c..1df4091 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -102,12 +102,14 @@
KeyguardStateController.class);
private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
+ private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
static {
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
- PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+ CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+ CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_BUFFERING);
}
private final NotificationVisibilityProvider mVisibilityProvider;
@@ -363,7 +365,17 @@
* @return true if playing
*/
public static boolean isPlayingState(int state) {
- return !PAUSED_MEDIA_STATES.contains(state);
+ return !PAUSED_MEDIA_STATES.contains(state)
+ && !CONNECTING_MEDIA_STATES.contains(state);
+ }
+
+ /**
+ * Check if a state should be considered as connecting
+ * @param state a PlaybackState
+ * @return true if connecting or buffering
+ */
+ public static boolean isConnectingState(int state) {
+ return CONNECTING_MEDIA_STATES.contains(state);
}
public void setUpWithPresenter(NotificationPresenter presenter) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index eaa66bb..633786f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -594,16 +594,13 @@
} else {
shouldClipOwnTop = view.showingPulsing();
}
- if (viewEnd > notificationClipEnd && !shouldClipOwnTop
- && (mAmbientState.isShadeExpanded() || !isPinned)) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
- if (isPinned) {
- clipBottomAmount = Math.min(view.getIntrinsicHeight() - view.getCollapsedHeight(),
- clipBottomAmount);
+ if (!isPinned) {
+ if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
+ int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ view.setClipBottomAmount(clipBottomAmount);
+ } else {
+ view.setClipBottomAmount(0);
}
- view.setClipBottomAmount(clipBottomAmount);
- } else {
- view.setClipBottomAmount(0);
}
if (shouldClipOwnTop) {
return (int) (viewEnd - getTranslationY());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index b95d153..7f3381c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -34,10 +34,13 @@
private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
+ private static final int TAG_ANIMATOR_BOTTOM_INSET = R.id.bottom_inset_animator_tag;
private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
+ private static final int TAG_END_BOTTOM_INSET = R.id.bottom_inset_animator_end_value_tag;
private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
+ private static final int TAG_START_BOTTOM_INSET = R.id.bottom_inset_animator_start_value_tag;
// These are flags such that we can create masks for filtering.
@@ -96,12 +99,17 @@
public boolean headsUpIsVisible;
/**
- * How much the child overlaps with the previous child on top. This is used to
- * show the background properly when the child on top is translating away.
+ * How much the child overlaps on top with the child above.
*/
public int clipTopAmount;
/**
+ * How much the child overlaps on bottom with the child above. This is used to
+ * show the background properly when the child on top is translating away.
+ */
+ public int clipBottomAmount;
+
+ /**
* The index of the view, only accounting for views not equal to GONE
*/
public int notGoneIndex;
@@ -138,8 +146,8 @@
if (view instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) view;
- int height = expandableView.getActualHeight();
- int newHeight = this.height;
+ final int height = expandableView.getActualHeight();
+ final int newHeight = this.height;
// apply height
if (height != newHeight) {
@@ -157,10 +165,14 @@
expandableView.setBelowSpeedBump(this.belowSpeedBump);
// apply clipping
- float oldClipTopAmount = expandableView.getClipTopAmount();
+ final float oldClipTopAmount = expandableView.getClipTopAmount();
if (oldClipTopAmount != this.clipTopAmount) {
expandableView.setClipTopAmount(this.clipTopAmount);
}
+ final float oldClipBottomAmount = expandableView.getClipBottomAmount();
+ if (oldClipBottomAmount != this.clipBottomAmount) {
+ expandableView.setClipBottomAmount(this.clipBottomAmount);
+ }
expandableView.setTransformingInShelf(false);
expandableView.setInShelf(inShelf);
@@ -187,13 +199,20 @@
abortAnimation(child, TAG_ANIMATOR_HEIGHT);
}
- // start top inset animation
+ // start clip top animation
if (this.clipTopAmount != expandableView.getClipTopAmount()) {
- startInsetAnimation(expandableView, properties);
+ startClipAnimation(expandableView, properties, /* clipTop */true);
} else {
abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
}
+ // start clip bottom animation
+ if (this.clipBottomAmount != expandableView.getClipBottomAmount()) {
+ startClipAnimation(expandableView, properties, /* clipTop */ false);
+ } else {
+ abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
+ }
+
// start dimmed animation
expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
@@ -301,16 +320,20 @@
child.setActualHeightAnimating(true);
}
- private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
- Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
- Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
- int newEndValue = this.clipTopAmount;
+ private void startClipAnimation(final ExpandableView child, AnimationProperties properties,
+ boolean clipTop) {
+ Integer previousStartValue = getChildTag(child,
+ clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET);
+ Integer previousEndValue = getChildTag(child,
+ clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET);
+ int newEndValue = clipTop ? this.clipTopAmount : this.clipBottomAmount;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
- ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
+ ValueAnimator previousAnimator = getChildTag(child,
+ clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET);
AnimationFilter filter = properties.getAnimationFilter();
- if (!filter.animateTopInset) {
+ if (clipTop && !filter.animateTopInset || !clipTop) {
// just a local update was performed
if (previousAnimator != null) {
// we need to increase all animation keyframes of the previous animator by the
@@ -319,22 +342,28 @@
int relativeDiff = newEndValue - previousEndValue;
int newStartValue = previousStartValue + relativeDiff;
values[0].setIntValues(newStartValue, newEndValue);
- child.setTag(TAG_START_TOP_INSET, newStartValue);
- child.setTag(TAG_END_TOP_INSET, newEndValue);
+ child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, newStartValue);
+ child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, newEndValue);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
return;
} else {
// no new animation needed, let's just apply the value
- child.setClipTopAmount(newEndValue);
+ if (clipTop) {
+ child.setClipTopAmount(newEndValue);
+ } else {
+ child.setClipBottomAmount(newEndValue);
+ }
return;
}
}
- ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
+ ValueAnimator animator = ValueAnimator.ofInt(
+ clipTop ? child.getClipTopAmount() : child.getClipBottomAmount(), newEndValue);
+ animator.addUpdateListener(animation -> {
+ if (clipTop) {
child.setClipTopAmount((int) animation.getAnimatedValue());
+ } else {
+ child.setClipBottomAmount((int) animation.getAnimatedValue());
}
});
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -353,15 +382,16 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- child.setTag(TAG_ANIMATOR_TOP_INSET, null);
- child.setTag(TAG_START_TOP_INSET, null);
- child.setTag(TAG_END_TOP_INSET, null);
+ child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET, null);
+ child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, null);
+ child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, null);
}
});
startAnimator(animator, listener);
- child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
- child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
- child.setTag(TAG_END_TOP_INSET, newEndValue);
+ child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET:TAG_ANIMATOR_BOTTOM_INSET, animator);
+ child.setTag(clipTop ? TAG_START_TOP_INSET: TAG_START_BOTTOM_INSET,
+ clipTop ? child.getClipTopAmount() : child.getClipBottomAmount());
+ child.setTag(clipTop ? TAG_END_TOP_INSET: TAG_END_BOTTOM_INSET, newEndValue);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e1f8c35..c097133 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -192,6 +192,7 @@
float clipStart = 0;
int childCount = algorithmState.visibleChildren.size();
boolean firstHeadsUp = true;
+ float firstHeadsUpEnd = 0;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState state = child.getViewState();
@@ -203,14 +204,18 @@
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
if (mClipNotificationScrollToTop
- && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
- && newYTranslation < clipStart
+ && ((isHeadsUp && !firstHeadsUp) || child.isHeadsUpAnimatingAway())
+ && newNotificationEnd > firstHeadsUpEnd
&& !ambientState.isShadeExpanded()) {
- // The previous view is overlapping on top, clip!
- float overlapAmount = clipStart - newYTranslation;
- state.clipTopAmount = (int) overlapAmount;
+ // The bottom of this view is peeking out from under the previous view.
+ // Clip the part that is peeking out.
+ float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
+ state.clipBottomAmount = (int) overlapAmount;
} else {
- state.clipTopAmount = 0;
+ state.clipBottomAmount = 0;
+ }
+ if (firstHeadsUp) {
+ firstHeadsUpEnd = newNotificationEnd;
}
if (isHeadsUp) {
firstHeadsUp = false;
@@ -635,8 +640,6 @@
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
childState.height = row.getIntrinsicHeight();
- childState.yTranslation = Math.min(topState.yTranslation + topState.height
- - childState.height, childState.yTranslation);
}
// heads up notification show and this row is the top entry of heads up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index 745228e..491e9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -168,14 +168,7 @@
private fun updateBottomSpacing() {
val (containerPadding, notificationsMargin) = calculateBottomSpacing()
var qsScrollPaddingBottom = 0
- val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
- if (!newFooter && !(splitShadeEnabled || isQSCustomizing || isQSDetailShowing ||
- isGestureNavigation || taskbarVisible)) {
- // no taskbar, portrait, navigation buttons enabled:
- // padding is needed so QS can scroll up over bottom insets - to reach the point when
- // the whole QS is above bottom insets
- qsScrollPaddingBottom = bottomStableInsets
- } else if (newFooter && !(isQSCustomizing || isQSDetailShowing)) {
+ if (!(isQSCustomizing || isQSDetailShowing)) {
// With the new footer, we also want this padding in the bottom in these cases
qsScrollPaddingBottom = if (splitShadeEnabled) {
notificationsMargin - scrimShadeBottomMargin
@@ -185,11 +178,7 @@
}
mView.setPadding(0, 0, 0, containerPadding)
mView.setNotificationsMarginBottom(notificationsMargin)
- if (newFooter) {
- mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
- } else {
- mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
- }
+ mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
}
private fun calculateBottomSpacing(): Pair<Int, Int> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 7caea06..2446cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -103,16 +103,6 @@
mStackScroller.setLayoutParams(params);
}
- public void setQSScrollPaddingBottom(int paddingBottom) {
- if (mQSScrollView != null) {
- mQSScrollView.setPaddingRelative(
- mQSScrollView.getPaddingLeft(),
- mQSScrollView.getPaddingTop(),
- mQSScrollView.getPaddingRight(),
- paddingBottom);
- }
- }
-
public void setQSContainerPaddingBottom(int paddingBottom) {
if (mQSContainer != null) {
mQSContainer.setPadding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 6fe92fa..87ca942 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -489,7 +489,7 @@
ExpandableNotificationRow row,
boolean animate,
boolean isActivityIntent) {
- mLogger.logStartNotificationIntent(entry.getKey(), intent);
+ mLogger.logStartNotificationIntent(entry.getKey());
try {
Runnable onFinishAnimationCallback = animate
? () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry)
@@ -513,8 +513,10 @@
mKeyguardStateController.isShowing(),
eventTime)
: getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
- return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+ int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
+ mLogger.logSendPendingIntent(entry.getKey(), intent, result);
+ return result;
});
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index d118747..2fbe520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -32,7 +32,7 @@
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(1/4) onNotificationClicked: $str1"
+ "(1/5) onNotificationClicked: $str1"
})
}
@@ -40,7 +40,7 @@
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+ "(2/5) handleNotificationClickAfterKeyguardDismissed: $str1"
})
}
@@ -48,16 +48,25 @@
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+ "(3/5) handleNotificationClickAfterPanelCollapsed: $str1"
})
}
- fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+ fun logStartNotificationIntent(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "(4/5) startNotificationIntent: $str1"
+ })
+ }
+
+ fun logSendPendingIntent(key: String, pendingIntent: PendingIntent, result: Int) {
buffer.log(TAG, INFO, {
str1 = key
str2 = pendingIntent.intent.toString()
+ int1 = result
}, {
- "(4/4) Starting $str2 for notification $str1"
+ "(5/5) Started intent $str2 for notification $str1 with result code $int1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index fe08fb0..217a613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1215,7 +1215,8 @@
}
// Use broadcast instead of ShadeController, as this dialog may have started in
// another process and normal dagger bindings are not available
- mBroadcastSender.closeSystemDialogs();
+ mBroadcastSender.sendBroadcastAsUser(
+ new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
getContext().startActivityAsUser(
CreateUserActivity.createIntentForStart(getContext()),
mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
deleted file mode 100644
index cfceefa..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util
-
-import android.content.Context
-import android.content.res.Configuration
-import android.util.AttributeSet
-import android.util.DisplayMetrics
-import android.util.TypedValue
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.systemui.R
-
-/**
- * Horizontal [LinearLayout] to contain some text.
- *
- * The height of this container can alternate between two different heights, depending on whether
- * the text takes one line or more.
- *
- * When the text takes multiple lines, it will use the values in the regular attributes (`padding`,
- * `layout_height`). The single line behavior must be set in XML.
- *
- * XML attributes for single line behavior:
- * * `systemui:textViewId`: set the id for the [TextView] that determines the height of the
- * container
- * * `systemui:singleLineHeight`: sets the height of the view when the text takes up only one line.
- * By default, it will use [getMinimumHeight].
- * * `systemui:singleLineVerticalPadding`: sets the padding (top and bottom) when then text takes up
- * only one line. By default, it is 0.
- *
- * All dimensions are updated when configuration changes.
- */
-class DualHeightHorizontalLinearLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttrs: Int = 0,
- defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
-
- private val singleLineHeightValue: TypedValue?
- private var singleLineHeightPx = 0
-
- private val singleLineVerticalPaddingValue: TypedValue?
- private var singleLineVerticalPaddingPx = 0
-
- private val textViewId: Int
- private var textView: TextView? = null
-
- private val displayMetrics: DisplayMetrics
- get() = context.resources.displayMetrics
-
- private var initialPadding = mPaddingTop // All vertical padding is the same
-
- private var originalMaxLines = 1
- var alwaysSingleLine: Boolean = false
- set(value) {
- field = value
- if (field) {
- textView?.setSingleLine()
- } else {
- textView?.maxLines = originalMaxLines
- }
- }
-
- init {
- if (orientation != HORIZONTAL) {
- throw IllegalStateException("This view should always have horizontal orientation")
- }
-
- val ta = context.obtainStyledAttributes(
- attrs,
- R.styleable.DualHeightHorizontalLinearLayout, defStyleAttrs, defStyleRes
- )
-
- val tempHeight = TypedValue()
- singleLineHeightValue = if (
- ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight)
- ) {
- ta.getValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight, tempHeight)
- tempHeight
- } else {
- null
- }
-
- val tempPadding = TypedValue()
- singleLineVerticalPaddingValue = if (
- ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding)
- ) {
- ta.getValue(
- R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding,
- tempPadding
- )
- tempPadding
- } else {
- null
- }
-
- textViewId = ta.getResourceId(R.styleable.DualHeightHorizontalLinearLayout_textViewId, 0)
-
- ta.recycle()
- }
-
- init {
- updateResources()
- }
-
- override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
- super.setPadding(left, top, right, bottom)
- initialPadding = top
- }
-
- override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
- super.setPaddingRelative(start, top, end, bottom)
- initialPadding = top
- }
-
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- textView?.let { tv ->
- if (tv.lineCount < 2 || alwaysSingleLine) {
- setMeasuredDimension(measuredWidth, singleLineHeightPx)
- mPaddingBottom = 0
- mPaddingTop = 0
- } else {
- mPaddingBottom = initialPadding
- mPaddingTop = initialPadding
- }
- }
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- textView = findViewById<TextView>(textViewId)?.also {
- originalMaxLines = it.maxLines
- }
- }
-
- override fun onConfigurationChanged(newConfig: Configuration?) {
- super.onConfigurationChanged(newConfig)
- updateResources()
- }
-
- override fun setOrientation(orientation: Int) {
- if (orientation == VERTICAL) {
- throw IllegalStateException("This view should always have horizontal orientation")
- }
- super.setOrientation(orientation)
- }
-
- private fun updateResources() {
- updateDimensionValue(singleLineHeightValue, minimumHeight, ::singleLineHeightPx::set)
- updateDimensionValue(singleLineVerticalPaddingValue, 0, ::singleLineVerticalPaddingPx::set)
- }
-
- private inline fun updateDimensionValue(
- tv: TypedValue?,
- defaultValue: Int,
- propertySetter: (Int) -> Unit
- ) {
- val value = tv?.let {
- if (it.resourceId != 0) {
- context.resources.getDimensionPixelSize(it.resourceId)
- } else {
- it.getDimension(displayMetrics).toInt()
- }
- } ?: defaultValue
- propertySetter(value)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4d34aa3..b70220d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -576,7 +576,9 @@
@Override
public void onEntryRemoved(NotificationEntry entry,
@NotifCollection.CancellationReason int reason) {
- BubblesManager.this.onEntryRemoved(entry);
+ if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL) {
+ BubblesManager.this.onEntryRemoved(entry);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 4cca38a..cc606de 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -88,6 +88,7 @@
bouncerFullyShown = false,
faceAuthenticated = false,
faceDisabled = false,
+ goingToSleep = false,
keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 26296d6..67fe044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -52,6 +52,7 @@
@Mock lateinit var panel: MediaControlPanel
@Mock lateinit var visualStabilityProvider: VisualStabilityProvider
@Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+ @Mock lateinit var mediaHostState: MediaHostState
@Mock lateinit var activityStarter: ActivityStarter
@Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@@ -188,4 +189,40 @@
verify(logger).logCarouselSettings()
}
+
+ @Test
+ fun testLocationChangeQs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QS,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+ }
+
+ @Test
+ fun testLocationChangeQqs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QQS,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
+ }
+
+ @Test
+ fun testLocationChangeLockscreen_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ }
+
+ @Test
+ fun testLocationChangeDream_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index f2e3edb..9116716 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.PendingIntent
import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.graphics.Color
@@ -749,6 +750,20 @@
}
@Test
+ fun tapContentView_isLogged() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+ val data = mediaData.copy(clickIntent = pendingIntent)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+ verify(viewHolder.player).setOnClickListener(captor.capture())
+
+ captor.value.onClick(viewHolder.player)
+
+ verify(logger).logTapContentView(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
fun logSeek() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index b9ff877..1921cb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -726,6 +726,25 @@
}
@Test
+ fun testPlaybackActions_connecting() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY
+ val stateBuilder = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_BUFFERING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+ addNotificationAndLoad()
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+ val actions = mediaDataCaptor.value!!.semanticActions!!
+
+ assertThat(actions.playOrPause).isNotNull()
+ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_connecting))
+ }
+
+ @Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index e6f48ec..10eeb11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -265,20 +265,58 @@
}
@Test
- fun onAboutToConnectDeviceChangedWithNonNullParams() {
+ fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
- val deviceCallback = captureCallback()
+ // Ensure we'll get device info when using the address
+ val fullMediaDevice = mock(MediaDevice::class.java)
+ val address = "fakeAddress"
+ val nameFromDevice = "nameFromDevice"
+ val iconFromDevice = mock(Drawable::class.java)
+ whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
+ whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
+ whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
+
// WHEN the about-to-connect device changes to non-null
+ val deviceCallback = captureCallback()
+ val nameFromParam = "nameFromParam"
+ val iconFromParam = mock(Drawable::class.java)
+ deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
+ assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+
+ // THEN the about-to-connect device based on the address is returned
+ val data = captureDeviceData(KEY)
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(nameFromDevice)
+ assertThat(data.name).isNotEqualTo(nameFromParam)
+ assertThat(data.icon).isEqualTo(iconFromDevice)
+ assertThat(data.icon).isNotEqualTo(iconFromParam)
+ }
+
+ @Test
+ fun onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddress() {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ // Run and reset the executors and listeners so we only focus on new events.
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(listener)
+
+ // Ensure we can't get device info based on the address
+ val address = "fakeAddress"
+ whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(null)
+
+ // WHEN the about-to-connect device changes to non-null
+ val deviceCallback = captureCallback()
val name = "AboutToConnectDeviceName"
val mockIcon = mock(Drawable::class.java)
- deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
+ deviceCallback.onAboutToConnectDeviceAdded(address, name, mockIcon)
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
- // THEN the about-to-connect device is returned
+
+ // THEN the about-to-connect device based on the parameters is returned
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(name)
@@ -286,21 +324,21 @@
}
@Test
- fun onAboutToConnectDeviceChangedWithNullParams() {
+ fun onAboutToConnectDeviceAddedThenRemoved_usesNormalDevice() {
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
val deviceCallback = captureCallback()
// First set a non-null about-to-connect device
- deviceCallback.onAboutToConnectDeviceChanged(
- "AboutToConnectDeviceName", mock(Drawable::class.java)
+ deviceCallback.onAboutToConnectDeviceAdded(
+ "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java)
)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
- // WHEN the about-to-connect device changes to null
- deviceCallback.onAboutToConnectDeviceChanged(null, null)
+ // WHEN hasDevice switches to false
+ deviceCallback.onAboutToConnectDeviceRemoved()
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
// THEN the normal device is returned
val data = captureDeviceData(KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
index 88c4514..27c039d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
@@ -24,7 +24,7 @@
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.settingslib.media.DeviceIconUtil
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.R
@@ -95,7 +95,7 @@
muteAwaitConnectionManager.startListening()
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -104,7 +104,9 @@
muteAwaitConnectionManager.startListening()
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+ eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+ )
}
@Test
@@ -114,7 +116,7 @@
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -125,7 +127,9 @@
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+ eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+ )
}
@Test
@@ -135,7 +139,7 @@
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -155,7 +159,7 @@
)
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -167,7 +171,7 @@
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -179,7 +183,7 @@
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
+ verify(localMediaManager).dispatchAboutToConnectDeviceRemoved()
}
private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
@@ -191,11 +195,12 @@
}
}
+private const val DEVICE_ADDRESS = "DeviceAddress"
private const val DEVICE_NAME = "DeviceName"
private val DEVICE = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
- "address",
+ DEVICE_ADDRESS,
DEVICE_NAME,
listOf(),
listOf(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 7b17c36..35d0024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -8,20 +8,20 @@
import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.systemui.R
import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.LeakCheckedTest
import com.google.common.truth.Truth.assertThat
@@ -29,10 +29,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -65,11 +69,12 @@
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var securityFooterController: QSSecurityFooter
@Mock
private lateinit var fgsManagerController: QSFgsManagerFooter
+ @Captor
+ private lateinit var visibilityChangedCaptor:
+ ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
private lateinit var controller: FooterActionsController
@@ -78,6 +83,8 @@
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeSettings: FakeSettings
+ private lateinit var securityFooter: View
+ private lateinit var fgsFooter: View
@Before
fun setUp() {
@@ -87,9 +94,14 @@
whenever(multiUserSwitchControllerFactory.create(any()))
.thenReturn(multiUserSwitchController)
- whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false)
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
+ securityFooter = View(mContext)
+ fgsFooter = View(mContext)
+
+ whenever(securityFooterController.view).thenReturn(securityFooter)
+ whenever(fgsManagerController.view).thenReturn(fgsFooter)
+
view = inflateView()
controller = constructFooterActionsController(view)
@@ -107,6 +119,13 @@
}
@Test
+ fun testInitializesControllers() {
+ verify(multiUserSwitchController).init()
+ verify(fgsManagerController).init()
+ verify(securityFooterController).init()
+ }
+
+ @Test
fun testLogPowerMenuClick() {
controller.visible = true
falsingManager.setFalseTap(false)
@@ -182,6 +201,10 @@
@Test
fun testCleanUpGAD() {
reset(globalActionsDialogProvider)
+ // We are creating a new controller, so detach the views from it
+ (securityFooter.parent as ViewGroup).removeView(securityFooter)
+ (fgsFooter.parent as ViewGroup).removeView(fgsFooter)
+
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
val view = inflateView()
controller = constructFooterActionsController(view)
@@ -198,6 +221,80 @@
verify(globalActionsDialog).destroy()
}
+ @Test
+ fun testSeparatorVisibility_noneVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_onlySecurityFooterVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_onlyFgsFooterVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_bothVisible_visible() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+ assertThat(separator.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testFgsFooterCollapsed() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+
+ val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+
+ clearInvocations(fgsManagerController)
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+ verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+ assertThat(booleanCaptor.allValues.last()).isFalse()
+
+ clearInvocations(fgsManagerController)
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+ verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+ assertThat(booleanCaptor.allValues.last()).isTrue()
+ }
+
+ private fun setVisibilities(
+ securityFooterVisible: Boolean,
+ fgsFooterVisible: Boolean,
+ listener: VisibilityChangedDispatcher.OnVisibilityChangedListener
+ ) {
+ securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE
+ listener.onVisibilityChanged(securityFooter.visibility)
+ fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE
+ listener.onVisibilityChanged(fgsFooter.visibility)
+ }
+
private fun inflateView(): FooterActionsView {
return LayoutInflater.from(context)
.inflate(R.layout.footer_actions, null) as FooterActionsView
@@ -208,6 +305,6 @@
activityStarter, userManager, userTracker, userInfoController,
deviceProvisionedController, securityFooterController, fgsManagerController,
falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
- showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper), featureFlags)
+ showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
index bf82e90..489c8c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
@@ -59,22 +59,14 @@
fun testContainerBottomPadding() {
qsContainer.updateResources(
qsPanelController,
- quickStatusBarHeaderController,
- /* newFooter */ false
- )
- verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0))
-
- qsContainer.updateResources(
- qsPanelController,
- quickStatusBarHeaderController,
- /* newFooter */ true
+ quickStatusBarHeaderController
)
verify(qsPanelContainer)
.setPaddingRelative(
anyInt(),
anyInt(),
anyInt(),
- eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+ eq(mContext.resources.getDimensionPixelSize(R.dimen.footer_actions_height))
)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 58a070d..689de50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -35,8 +34,6 @@
class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var qsPanel: QSPanel
- @Mock private lateinit var qsFgsManagerFooter: QSFgsManagerFooter
- @Mock private lateinit var qsSecurityFooter: QSSecurityFooter
@Mock private lateinit var tunerService: TunerService
@Mock private lateinit var qsTileHost: QSTileHost
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@@ -50,7 +47,6 @@
@Mock private lateinit var brightnessSlider: BrightnessSliderController
@Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory
@Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
@@ -69,8 +65,6 @@
controller = QSPanelController(
qsPanel,
- qsFgsManagerFooter,
- qsSecurityFooter,
tunerService,
qsTileHost,
qsCustomizerController,
@@ -84,7 +78,6 @@
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
- featureFlags,
statusBarKeyguardViewManager
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index ba02a82..e237a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -13,9 +13,6 @@
*/
package com.android.systemui.qs
-import android.content.res.Configuration
-import android.content.res.Configuration.ORIENTATION_LANDSCAPE
-import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
@@ -59,8 +56,6 @@
mFooter = LinearLayout(mContext).apply { id = R.id.qs_footer }
mQsPanel.addView(mFooter)
mQsPanel.onFinishInflate()
- mQsPanel.setSecurityFooter(View(mContext), false)
- mQsPanel.setHeaderContainer(LinearLayout(mContext))
// Provides a parent with non-zero size for QSPanel
mParentView = FrameLayout(mContext).apply {
addView(mQsPanel)
@@ -69,49 +64,6 @@
}
@Test
- fun testSecurityFooter_appearsOnBottomOnSplitShade() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
- mQsPanel.switchSecurityFooter(true)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // After mFooter
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
- mQsPanel.indexOfChild(mFooter) + 1
- )
- }
-
- @Test
- fun testSecurityFooter_appearsOnBottomIfPortrait() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_PORTRAIT))
- mQsPanel.switchSecurityFooter(false)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // After mFooter
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
- mQsPanel.indexOfChild(mFooter) + 1
- )
- }
-
- @Test
- fun testSecurityFooter_appearsOnTopIfSmallScreenAndLandscape() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
- mQsPanel.switchSecurityFooter(false)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // -1 means that it is part of the mHeaderContainer
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
- }
-
- @Test
fun testHasCollapseAccessibilityAction() {
val info = AccessibilityNodeInfo(mQsPanel)
mQsPanel.onInitializeAccessibilityNodeInfo(info)
@@ -128,7 +80,4 @@
mQsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null)
verify(mockRunnable).run()
}
-
- private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
- context.resources.configuration.apply { orientation = newOrientation }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
index 60c2bde..a6a584d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
@@ -2,11 +2,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.FrameLayout
-import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth
@@ -37,8 +35,6 @@
quickQSPanel.initialize()
quickQSPanel.onFinishInflate()
- quickQSPanel.setSecurityFooter(View(mContext), false)
- quickQSPanel.setHeaderContainer(LinearLayout(mContext))
// Provides a parent with non-zero size for QSPanel
parentView = FrameLayout(mContext).apply {
addView(quickQSPanel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 05a21db..e2fabbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -13,7 +13,6 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.recents.OverviewProxyService
@@ -114,25 +113,6 @@
@Test
fun testTaskbarVisibleInSplitShade() {
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarVisibleInSplitShade_newFooter() {
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
@@ -153,25 +133,6 @@
fun testTaskbarNotVisibleInSplitShade() {
// when taskbar is not visible, it means we're on the home screen
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShade_newFooter() {
- // when taskbar is not visible, it means we're on the home screen
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -190,24 +151,6 @@
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout())
- then(expectedContainerPadding = CUTOUT_HEIGHT)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -226,23 +169,6 @@
@Test
fun testTaskbarVisibleInSinglePaneShade() {
disableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarVisibleInSinglePaneShade_newFooter() {
- disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
@@ -260,28 +186,6 @@
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
disableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = CUTOUT_HEIGHT)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
- disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -303,27 +207,6 @@
fun testCustomizingInSinglePaneShade() {
disableSplitShade()
controller.setCustomizerShowing(true)
- useNewFooter(false)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testCustomizingInSinglePaneShade_newFooter() {
- disableSplitShade()
- controller.setCustomizerShowing(true)
- useNewFooter(true)
// always sets spacings to 0
given(taskbarVisible = false,
@@ -343,27 +226,6 @@
fun testDetailShowingInSinglePaneShade() {
disableSplitShade()
controller.setDetailShowing(true)
- useNewFooter(false)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testDetailShowingInSinglePaneShade_newFooter() {
- disableSplitShade()
- controller.setDetailShowing(true)
- useNewFooter(true)
// always sets spacings to 0
given(taskbarVisible = false,
@@ -383,25 +245,6 @@
fun testDetailShowingInSplitShade() {
enableSplitShade()
controller.setDetailShowing(true)
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- // should not influence spacing
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
- }
-
- @Test
- fun testDetailShowingInSplitShade_newFooter() {
- enableSplitShade()
- controller.setDetailShowing(true)
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -531,7 +374,6 @@
@Test
fun testWindowInsetDebounce() {
disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -596,13 +438,8 @@
verify(notificationsQSContainer)
.setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
- if (newFooter) {
- verify(notificationsQSContainer)
+ verify(notificationsQSContainer)
.setQSContainerPaddingBottom(expectedQsPadding)
- } else {
- verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
- }
Mockito.clearInvocations(notificationsQSContainer)
}
@@ -620,10 +457,6 @@
return this
}
- private fun useNewFooter(useNewFooter: Boolean) {
- whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
- }
-
private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
return constraintSetCaptor.value.getConstraint(id).layout
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 40657fb..ce7924a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -807,7 +807,7 @@
assertTrue(mBubbleController.hasBubbles());
// Removes the notification
- mEntryListener.onEntryRemoved(mRow, 0);
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
assertFalse(mBubbleController.hasBubbles());
}
@@ -938,7 +938,7 @@
mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
// WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
+ mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
// THEN the summary and its children are removed from bubble data
assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 7e3ede1..d75d648 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -34,6 +34,7 @@
import java.util.List;
import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -374,7 +375,8 @@
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
return future.get(600, TimeUnit.SECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ } catch (InterruptedException | ExecutionException | TimeoutException
+ | CancellationException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
return null;
} finally {
@@ -403,7 +405,11 @@
void cancelActiveFutures() {
synchronized (mActiveFuturesLock) {
for (AndroidFuture<?> future : mActiveFutures) {
- future.cancel(true);
+ try {
+ future.cancel(true);
+ } catch (CancellationException ignored) {
+ // This is expected, so ignore the exception.
+ }
}
mActiveFutures.clear();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 96761ca..07c2818 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17215,17 +17215,17 @@
}
@Override
- public boolean isUidCurrentlyInstrumented(int uid) {
+ public int getInstrumentationSourceUid(int uid) {
synchronized (mProcLock) {
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
if (!activeInst.mFinished && activeInst.mTargetInfo != null
&& activeInst.mTargetInfo.uid == uid) {
- return true;
+ return activeInst.mSourceUid;
}
}
}
- return false;
+ return INVALID_UID;
}
@Override
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 032b129..64ff532 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -49,6 +49,7 @@
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.os.AppBackgroundRestrictionsInfo;
import android.os.AppBatteryStatsProto;
import android.os.BatteryConsumer;
import android.os.BatteryConsumer.Dimensions;
@@ -74,6 +75,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
import com.android.server.am.AppRestrictionController.TrackerType;
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
@@ -175,6 +177,12 @@
@GuardedBy("mLock")
private long mLastUidBatteryUsageStartTs;
+ /**
+ * elapseRealTime of last time the AppBatteryTracker is reported to statsd.
+ */
+ @GuardedBy("mLock")
+ private long mLastReportTime = 0;
+
// For debug only.
private final SparseArray<ImmutableBatteryUsage> mDebugUidPercentages = new SparseArray<>();
@@ -228,9 +236,92 @@
mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
}
}
+ logAppBatteryTrackerIfNeeded();
}
}
+ /**
+ * Log per-uid BatteryTrackerInfo to statsd every 24 hours (as the window specified in
+ * {@link AppBatteryPolicy#mBgCurrentDrainWindowMs})
+ */
+ private void logAppBatteryTrackerIfNeeded() {
+ final long now = SystemClock.elapsedRealtime();
+ synchronized (mLock) {
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ if (now - mLastReportTime < bgPolicy.mBgCurrentDrainWindowMs) {
+ return;
+ } else {
+ mLastReportTime = now;
+ }
+ }
+ updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+ synchronized (mLock) {
+ for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
+ final int uid = mUidBatteryUsageInWindow.keyAt(i);
+ if (!UserHandle.isCore(uid) && !UserHandle.isApp(uid)) {
+ continue;
+ }
+ if (BATTERY_USAGE_NONE.equals(mUidBatteryUsageInWindow.valueAt(i))) {
+ continue;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO,
+ uid,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER,
+ null /*byte[] fgs_tracker_info*/,
+ getBatteryTrackerInfoProtoLocked(uid) /*byte[] battery_tracker_info*/,
+ null /*byte[] broadcast_events_tracker_info*/,
+ null /*byte[] bind_service_events_tracker_info*/,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN,
+ isLowRamDeviceStatic());
+ }
+ }
+ }
+
+ /**
+ * Get the BatteryTrackerInfo proto of a UID.
+ * @param uid
+ * @return byte array of the proto.
+ */
+ @NonNull byte[] getBatteryTrackerInfoProtoLocked(int uid) {
+ final ImmutableBatteryUsage temp = mUidBatteryUsageInWindow.get(uid);
+ if (temp == null) {
+ return new byte[0];
+ }
+ final BatteryUsage bgUsage = temp.calcPercentage(uid, mInjector.getPolicy());
+ final double allUsage = bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_UNSPECIFIED]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED];
+ final double usageBackground =
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND];
+ final double usageFgs =
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
+ Slog.d(TAG, "getBatteryTrackerInfoProtoLocked uid:" + uid
+ + " allUsage:" + String.format("%4.2f%%", allUsage)
+ + " usageBackground:" + String.format("%4.2f%%", usageBackground)
+ + " usageFgs:" + String.format("%4.2f%%", usageFgs));
+ final ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_24H,
+ allUsage * 10000);
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_BACKGROUND,
+ usageBackground * 10000);
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FGS,
+ usageFgs * 10000);
+ proto.flush();
+ return proto.getBytes();
+ }
+
@Override
void onUserStarted(final @UserIdInt int userId) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index f8378c3..ddd2764 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.server.am.AppFGSTracker.AppFGSPolicy;
import com.android.server.am.AppFGSTracker.PackageDurations;
import com.android.server.am.AppRestrictionController.TrackerType;
@@ -112,9 +113,14 @@
@Override
public void onForegroundServiceNotificationUpdated(String packageName, int uid,
- int foregroundId) {
- mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
- uid, foregroundId, packageName).sendToTarget();
+ int foregroundId, boolean canceling) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = uid;
+ args.argi2 = foregroundId;
+ args.arg1 = packageName;
+ args.arg2 = canceling ? Boolean.TRUE : Boolean.FALSE;
+ mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED, args)
+ .sendToTarget();
}
private static class MyHandler extends Handler {
@@ -149,8 +155,10 @@
(String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
+ final SomeArgs args = (SomeArgs) msg.obj;
mTracker.handleForegroundServiceNotificationUpdated(
- (String) msg.obj, msg.arg1, msg.arg2);
+ (String) args.arg1, args.argi1, args.argi2, (Boolean) args.arg2);
+ args.recycle();
break;
case MSG_CHECK_LONG_RUNNING_FGS:
mTracker.checkLongRunningFgs();
@@ -241,18 +249,18 @@
}
private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
- int notificationId) {
+ int notificationId, boolean canceling) {
synchronized (mLock) {
SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
- if (notificationId > 0) {
+ if (!canceling) {
if (notificationIDs == null) {
notificationIDs = new SparseBooleanArray();
mFGSNotificationIDs.put(uid, packageName, notificationIDs);
}
notificationIDs.put(notificationId, false);
- } else if (notificationId < 0) {
+ } else {
if (notificationIDs != null) {
- final int indexOfKey = notificationIDs.indexOfKey(-notificationId);
+ final int indexOfKey = notificationIDs.indexOfKey(notificationId);
if (indexOfKey >= 0) {
final boolean wasVisible = notificationIDs.valueAt(indexOfKey);
notificationIDs.removeAt(indexOfKey);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 708bd16..6f74359 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -1975,6 +1975,9 @@
}
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ if (pkg == null || pkg.applicationInfo == null) {
+ return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+ }
final int targetSdk = pkg.applicationInfo.targetSdkVersion;
if (targetSdk < Build.VERSION_CODES.S) {
return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_PRE_S;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index c70fe03..ade44ea 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -880,9 +880,9 @@
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
// exported, or are System level broadcasts
- if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid,
- r.callingUid, filter.receiverList.uid, filter.exported)
- != PackageManager.PERMISSION_GRANTED) {
+ if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid)
+ && filter.receiverList.uid != r.callingUid) {
+
Slog.w(TAG, "Exported Denial: sending "
+ r.intent.toString()
+ ", action: " + r.intent.getAction()
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 639f56c..5a55b8b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1112,7 +1112,7 @@
foregroundNoti = localForegroundNoti; // save it for amending next time
signalForegroundServiceNotification(packageName, appInfo.uid,
- localForegroundId);
+ localForegroundId, false /* canceling */);
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
@@ -1147,17 +1147,18 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Error canceling notification for service", e);
}
- signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
+ signalForegroundServiceNotification(packageName, appInfo.uid, localForegroundId,
+ true /* canceling */);
}
});
}
private void signalForegroundServiceNotification(String packageName, int uid,
- int foregroundId) {
+ int foregroundId, boolean canceling) {
synchronized (ams) {
for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
- packageName, appInfo.uid, foregroundId);
+ packageName, appInfo.uid, foregroundId, canceling);
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b..752e17e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2367,7 +2367,8 @@
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
if (!isSelfRequest) {
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
boolean isCallerPermissionController;
try {
@@ -6894,7 +6895,8 @@
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
if (!isCallerSystem && !isCallerInstrumented) {
return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5a105f5..5fcdc8a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -70,6 +70,8 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
+import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -708,9 +710,6 @@
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
- // Do not let constrain be overwritten by override from WindowManager.
- info.shouldConstrainMetricsForLauncher =
- display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher;
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
handleLogicalDisplayChangedLocked(display);
}
@@ -2212,21 +2211,6 @@
}
}
- void setShouldConstrainMetricsForLauncher(boolean constrain) {
- // Apply constrain for every display.
- synchronized (mSyncRoot) {
- int[] displayIds = mLogicalDisplayMapper.getDisplayIdsLocked(Process.myUid());
- for (int i : displayIds) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(i);
- if (display == null) {
- return;
- }
- display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher = constrain;
- setDisplayInfoOverrideFromWindowManagerInternal(i, display.getDisplayInfoLocked());
- }
- }
- }
-
void setDockedAndIdleEnabled(boolean enabled, int displayId) {
synchronized (mSyncRoot) {
final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index bfdac57..7dce238 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
- case "constrain-launcher-metrics":
- return setConstrainLauncherMetrics();
case "set-user-preferred-display-mode":
return setUserPreferredDisplayMode();
case "clear-user-preferred-display-mode":
@@ -112,9 +110,6 @@
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
- pw.println(" constrain-launcher-metrics [true|false]");
- pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
- pw.println(" Launcher.");
pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE "
+ "DISPLAY_ID (optional)");
pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
@@ -205,17 +200,6 @@
return 0;
}
- private int setConstrainLauncherMetrics() {
- String constrainText = getNextArg();
- if (constrainText == null) {
- getErrPrintWriter().println("Error: no value specified");
- return 1;
- }
- boolean constrain = Boolean.parseBoolean(constrainText);
- mService.setShouldConstrainMetricsForLauncher(constrain);
- return 0;
- }
-
private int setUserPreferredDisplayMode() {
final String widthText = getNextArg();
if (widthText == null) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b7ad4ed..a640497 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,8 +233,6 @@
info.displayCutout = mOverrideDisplayInfo.displayCutout;
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
- info.shouldConstrainMetricsForLauncher =
- mOverrideDisplayInfo.shouldConstrainMetricsForLauncher;
}
mInfo.set(info);
}
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
new file mode 100644
index 0000000..d7563e0
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+/**
+ * Action to query and track the audio status of the System Audio device when enabling or using
+ * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
+ * 1. When enabling AVC: queries the starting audio status of the System Audio device and
+ * enables the feature upon receiving a response.
+ * 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
+ * notifies AudioService if the audio status changes.
+ */
+final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
+ private static final String TAG = "AbsoluteVolumeAudioStatusAction";
+
+ private int mInitialAudioStatusRetriesLeft = 2;
+
+ private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
+ private static final int STATE_MONITOR_AUDIO_STATUS = 2;
+
+ private final int mTargetAddress;
+
+ private AudioStatus mLastAudioStatus;
+
+ AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) {
+ super(source);
+ mTargetAddress = targetAddress;
+ }
+
+ @Override
+ boolean start() {
+ mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS;
+ sendGiveAudioStatus();
+ return true;
+ }
+
+ void updateVolume(int volumeIndex) {
+ mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute());
+ }
+
+ private void sendGiveAudioStatus() {
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress));
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_REPORT_AUDIO_STATUS:
+ return handleReportAudioStatus(cmd);
+ }
+
+ return false;
+ }
+
+ private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
+ if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
+ return false;
+ }
+
+ boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+ int volume = HdmiUtils.getAudioStatusVolume(cmd);
+ AudioStatus audioStatus = new AudioStatus(volume, mute);
+ if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
+ localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
+ mState = STATE_MONITOR_AUDIO_STATUS;
+ } else if (mState == STATE_MONITOR_AUDIO_STATUS) {
+ if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
+ localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
+ }
+ if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
+ }
+ }
+ mLastAudioStatus = audioStatus;
+
+ return true;
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != state) {
+ return;
+ } else if (mInitialAudioStatusRetriesLeft > 0) {
+ mInitialAudioStatusRetriesLeft--;
+ sendGiveAudioStatus();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..438c1ea
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
+ * passes method calls to that instance.
+ */
+public class AudioDeviceVolumeManagerWrapper
+ implements AudioDeviceVolumeManagerWrapperInterface {
+
+ private static final String TAG = "AudioDeviceVolumeManagerWrapper";
+
+ private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+
+ public AudioDeviceVolumeManagerWrapper(Context context) {
+ mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+ }
+
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
+ mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
+ vclistener, handlesVolumeAdjustment);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
new file mode 100644
index 0000000..1a1d4c1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
+ * Allows the class to be faked for tests.
+ */
+public interface AudioDeviceVolumeManagerWrapperInterface {
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
+ * Executor, OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
+ * OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
+ * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ */
+ void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment);
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java
new file mode 100644
index 0000000..a884ffb
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioStatus.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Immutable representation of the information in the [Audio Status] operand:
+ * volume status (0 <= N <= 100) and mute status (muted or unmuted).
+ */
+public class AudioStatus {
+ public static final int MAX_VOLUME = 100;
+ public static final int MIN_VOLUME = 0;
+
+ int mVolume;
+ boolean mMute;
+
+ public AudioStatus(int volume, boolean mute) {
+ mVolume = volume;
+ mMute = mute;
+ }
+
+ public int getVolume() {
+ return mVolume;
+ }
+
+ public boolean getMute() {
+ return mMute;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof AudioStatus)) {
+ return false;
+ }
+
+ AudioStatus other = (AudioStatus) obj;
+ return mVolume == other.mVolume
+ && mMute == other.mMute;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mVolume, mMute);
+ }
+
+ @Override
+ public String toString() {
+ return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 751f2db..157057d 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -118,6 +118,7 @@
MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
MESSAGE_GIVE_AUDIO_STATUS,
MESSAGE_SET_SYSTEM_AUDIO_MODE,
+ MESSAGE_SET_AUDIO_VOLUME_LEVEL,
MESSAGE_REPORT_AUDIO_STATUS,
MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
@@ -197,9 +198,9 @@
static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
+ static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
- static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
static final int MESSAGE_ROUTING_CHANGE = 0x80;
static final int MESSAGE_ROUTING_INFORMATION = 0x81;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 26a1613..fb2d2ee 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -303,6 +303,13 @@
if (dispatchMessageToAction(message)) {
return Constants.HANDLED;
}
+
+ // If a message type has its own class, all valid messages of that type
+ // will be represented by an instance of that class.
+ if (message instanceof SetAudioVolumeLevelMessage) {
+ return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
+ }
+
switch (message.getOpcode()) {
case Constants.MESSAGE_ACTIVE_SOURCE:
return handleActiveSource(message);
@@ -637,6 +644,11 @@
return Constants.NOT_HANDLED;
}
+ @Constants.HandleMessageResult
+ protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+ return Constants.NOT_HANDLED;
+ }
+
@Constants.RcProfile
protected abstract int getRcProfile();
@@ -1002,6 +1014,57 @@
action.start();
}
+ void addAvcAudioStatusAction(int targetAddress) {
+ if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
+ addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
+ }
+ }
+
+ void removeAvcAudioStatusAction() {
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
+ }
+
+ void updateAvcVolume(int volumeIndex) {
+ for (AbsoluteVolumeAudioStatusAction action :
+ getActions(AbsoluteVolumeAudioStatusAction.class)) {
+ action.updateVolume(volumeIndex);
+ }
+ }
+
+ /**
+ * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
+ * in parallel: send <Give Features> (to get <Report Features> in response),
+ * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
+ */
+ @ServiceThreadOnly
+ void queryAvcSupport(int targetAddress) {
+ assertRunOnServiceThread();
+
+ // Send <Give Features> if using CEC 2.0 or above.
+ if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ synchronized (mLock) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
+ getDeviceInfo().getLogicalAddress(), targetAddress));
+ }
+ }
+
+ // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
+ // device, start one.
+ List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
+ getActions(SetAudioVolumeLevelDiscoveryAction.class);
+ if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
+ addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ if (result == HdmiControlManager.RESULT_SUCCESS) {
+ getService().checkAndUpdateAbsoluteVolumeControlState();
+ }
+ }
+ }));
+ }
+ }
+
@ServiceThreadOnly
void startQueuedActions() {
assertRunOnServiceThread();
@@ -1205,6 +1268,9 @@
*/
protected void disableDevice(
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
+ removeAction(SetAudioVolumeLevelDiscoveryAction.class);
+
mPendingActionClearedCallback =
new PendingActionClearedCallback() {
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 90b4f76..c0c0202 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -307,6 +307,7 @@
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
removeAction(OneTouchPlayAction.class);
removeAction(DevicePowerStatusAction.class);
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
super.disableDevice(initiatedByCec, callback);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9212fb6..1ea1457 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1166,6 +1166,19 @@
return Constants.HANDLED;
}
+ @Override
+ @Constants.HandleMessageResult
+ protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+ // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
+ // handle it when System Audio Mode is enabled.
+ if (mService.isSystemAudioActivated()) {
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
+ } else {
+ mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
+ return Constants.HANDLED;
+ }
+ }
+
void announceOneTouchRecordResult(int recorderAddress, int result) {
mService.invokeOneTouchRecordResult(recorderAddress, result);
}
@@ -1202,6 +1215,13 @@
return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
+ /**
+ * Returns the audio output device used for System Audio Mode.
+ */
+ AudioDeviceAttributes getSystemAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+ }
+
@ServiceThreadOnly
void handleRemoveActiveRoutingPath(int path) {
@@ -1296,6 +1316,7 @@
removeAction(OneTouchRecordAction.class);
removeAction(TimerRecordingAction.class);
removeAction(NewDeviceAction.class);
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
disableSystemAudioIfExist();
disableArcIfExist();
@@ -1318,7 +1339,6 @@
removeAction(SystemAudioActionFromAvr.class);
removeAction(SystemAudioActionFromTv.class);
removeAction(SystemAudioAutoInitiationAction.class);
- removeAction(SystemAudioStatusAction.class);
removeAction(VolumeControlAction.class);
if (!mService.isControlEnabled()) {
@@ -1589,6 +1609,7 @@
return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
.setRecordTvScreenSupport(FEATURE_SUPPORTED)
.setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
.build();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index 290cae5..2b84225 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -262,6 +262,8 @@
return "Give Audio Status";
case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
return "Set System Audio Mode";
+ case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
+ return "Set Audio Volume Level";
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
return "Report Audio Status";
case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 8b6d16a..caaf800 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,6 +259,7 @@
// The addition of a local device should not notify listeners
return;
}
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
return;
@@ -383,7 +384,7 @@
final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
assertRunOnServiceThread();
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
localDevice.mCecMessageCache.flushMessagesFrom(address);
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
@@ -586,6 +587,8 @@
.build();
updateCecDevice(newDeviceInfo);
+
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
}
@ServiceThreadOnly
@@ -617,6 +620,8 @@
)
.build();
updateCecDevice(newDeviceInfo);
+
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 12380ab..9824b4e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -30,6 +30,7 @@
import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -38,6 +39,7 @@
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiHotplugEvent;
@@ -56,7 +58,12 @@
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.hardware.tv.cec.V1_0.OptionKey;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
+import android.media.VolumeInfo;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.tv.TvInputManager;
@@ -83,6 +90,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
+import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -101,6 +109,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -202,6 +211,27 @@
public @interface WakeReason {
}
+ @VisibleForTesting
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
+ @VisibleForTesting
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HDMI_ARC, "");
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HDMI_EARC, "");
+
+ // Audio output devices used for Absolute Volume Control
+ private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
+ Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
+ AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
+
+ // AudioAttributes for STREAM_MUSIC
+ @VisibleForTesting
+ static final AudioAttributes STREAM_MUSIC_ATTRIBUTES =
+ new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
+
private final Executor mServiceThreadExecutor = new Executor() {
@Override
public void execute(Runnable r) {
@@ -209,6 +239,10 @@
}
};
+ Executor getServiceThreadExecutor() {
+ return mServiceThreadExecutor;
+ }
+
// Logical address of the active source.
@GuardedBy("mLock")
protected final ActiveSource mActiveSource = new ActiveSource();
@@ -222,6 +256,13 @@
@HdmiControlManager.VolumeControl
private int mHdmiCecVolumeControl;
+ // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
+ @GuardedBy("mLock")
+ private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
+
+ // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services.
+ private int mStreamMusicMaxVolume;
+
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
private HdmiCecConfig mHdmiCecConfig;
@@ -411,6 +452,12 @@
private PowerManagerInternalWrapper mPowerManagerInternal;
@Nullable
+ private AudioManager mAudioManager;
+
+ @Nullable
+ private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
+
+ @Nullable
private Looper mIoLooper;
@Nullable
@@ -439,11 +486,21 @@
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
- @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
+ /**
+ * Constructor for testing.
+ *
+ * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
+ * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
+ *
+ * @see FakeAudioDeviceVolumeManagerWrapper
+ */
+ @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
+ AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
super(context);
mLocalDevices = deviceTypes;
mSettingsObserver = new SettingsObserver(mHandler);
mHdmiCecConfig = new HdmiCecConfig(context);
+ mAudioDeviceVolumeManager = audioDeviceVolumeManager;
}
public HdmiControlService(Context context) {
@@ -744,6 +801,14 @@
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
mPowerManagerInternal = new PowerManagerInternalWrapper();
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ if (mAudioDeviceVolumeManager == null) {
+ mAudioDeviceVolumeManager =
+ new AudioDeviceVolumeManagerWrapper(getContext());
+ }
+ getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
+ mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
runOnServiceThread(this::bootCompleted);
}
@@ -2419,6 +2484,7 @@
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
pw.println("mIsCecAvailable: " + mIsCecAvailable);
pw.println("mCecVersion: " + mCecVersion);
+ pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
// System settings
pw.println("System_settings:");
@@ -2578,6 +2644,7 @@
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
mHdmiCecVolumeControl = hdmiCecVolumeControl;
announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
// Get the source address to send out commands to devices connected to the current device
@@ -3086,15 +3153,17 @@
private void announceHdmiCecVolumeControlFeatureChange(
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
assertRunOnServiceThread();
- mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
- try {
- listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
- } catch (RemoteException e) {
- Slog.e(TAG,
- "Failed to report HdmiControlVolumeControlStatusChange: "
- + hdmiCecVolumeControl);
- }
- });
+ synchronized (mLock) {
+ mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
+ try {
+ listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Failed to report HdmiControlVolumeControlStatusChange: "
+ + hdmiCecVolumeControl);
+ }
+ });
+ }
}
public HdmiCecLocalDeviceTv tv() {
@@ -3131,8 +3200,20 @@
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
+ /**
+ * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+ */
+ @Nullable
AudioManager getAudioManager() {
- return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ return mAudioManager;
+ }
+
+ /**
+ * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+ */
+ @Nullable
+ private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
+ return mAudioDeviceVolumeManager;
}
boolean isControlEnabled() {
@@ -3486,6 +3567,7 @@
synchronized (mLock) {
mSystemAudioActivated = on;
}
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
@ServiceThreadOnly
@@ -3599,6 +3681,8 @@
device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
deviceIsActiveSource, caller);
}
+
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
// This method should only be called when the device can be the active source
@@ -3791,4 +3875,332 @@
Slog.e(TAG, "Failed to report setting change", e);
}
}
+
+ /**
+ * Listener for changes to the volume behavior of an audio output device. Caches the
+ * volume behavior of devices used for Absolute Volume Control.
+ */
+ @VisibleForTesting
+ @ServiceThreadOnly
+ void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
+ assertRunOnServiceThread();
+ if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ synchronized (mLock) {
+ mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
+ }
+ checkAndUpdateAbsoluteVolumeControlState();
+ }
+ }
+
+ /**
+ * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
+ * results for the volume behaviors of HDMI audio devices.
+ */
+ @AudioManager.DeviceVolumeBehavior
+ private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
+ if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ synchronized (mLock) {
+ if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
+ return mAudioDeviceVolumeBehaviors.get(device);
+ }
+ }
+ }
+ return getAudioManager().getDeviceVolumeBehavior(device);
+ }
+
+ /**
+ * Returns whether Absolute Volume Control is enabled or not. This is determined by the
+ * volume behavior of the relevant HDMI audio output device(s) for this device's type.
+ */
+ public boolean isAbsoluteVolumeControlEnabled() {
+ if (!isTvDevice() && !isPlaybackDevice()) {
+ return false;
+ }
+ AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
+ if (avcAudioOutputDevice == null) {
+ return false;
+ }
+ return getDeviceVolumeBehavior(avcAudioOutputDevice)
+ == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ }
+
+ private AudioDeviceAttributes getAvcAudioOutputDevice() {
+ if (isTvDevice()) {
+ return tv().getSystemAudioOutputDevice();
+ } else if (isPlaybackDevice()) {
+ return AUDIO_OUTPUT_DEVICE_HDMI;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
+ * if necessary. AVC is enabled precisely when a specific audio output device
+ * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
+ * behavior.
+ *
+ * AVC must be enabled on a Playback device or TV precisely when it is playing
+ * audio on an external device (the System Audio device) that supports the feature.
+ * This reduces to these conditions:
+ *
+ * 1. If the System Audio Device is an Audio System: System Audio Mode is active
+ * 2. Our HDMI audio output device is using full volume behavior
+ * 3. CEC volume is enabled
+ * 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
+ *
+ * If not all of these conditions are met, this method disables AVC if necessary.
+ *
+ * If all of these conditions are met, this method starts an action to query the System Audio
+ * device's audio status, which enables AVC upon obtaining the audio status.
+ */
+ @ServiceThreadOnly
+ void checkAndUpdateAbsoluteVolumeControlState() {
+ assertRunOnServiceThread();
+
+ // Can't enable or disable AVC before we have access to system services
+ if (getAudioManager() == null) {
+ return;
+ }
+
+ HdmiCecLocalDevice localCecDevice;
+ if (isTvDevice() && tv() != null) {
+ localCecDevice = tv();
+ // Condition 1: TVs need System Audio Mode to be active
+ // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
+ // TV is the System Audio Device instead.)
+ if (!isSystemAudioActivated()) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+ } else if (isPlaybackDevice() && playback() != null) {
+ localCecDevice = playback();
+ } else {
+ // Either this device type doesn't support AVC, or it hasn't fully initialized yet
+ return;
+ }
+
+ HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
+ localCecDevice.findAudioReceiverAddress());
+ @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
+ getDeviceVolumeBehavior(getAvcAudioOutputDevice());
+
+ // Condition 2: Already using full or absolute volume behavior
+ boolean alreadyUsingFullOrAbsoluteVolume =
+ currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
+ || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ // Condition 3: CEC volume is enabled
+ boolean cecVolumeEnabled =
+ getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
+
+ if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+
+ // Check for safety: if the System Audio device is a candidate for AVC, we should already
+ // have received messages from it to trigger the other conditions.
+ if (systemAudioDeviceInfo == null) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+ // Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
+ switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
+ case DeviceFeatures.FEATURE_SUPPORTED:
+ if (!isAbsoluteVolumeControlEnabled()) {
+ // Start an action that will call {@link #enableAbsoluteVolumeControl}
+ // once the System Audio device sends <Report Audio Status>
+ localCecDevice.addAvcAudioStatusAction(
+ systemAudioDeviceInfo.getLogicalAddress());
+ }
+ return;
+ case DeviceFeatures.FEATURE_NOT_SUPPORTED:
+ disableAbsoluteVolumeControl();
+ return;
+ case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
+ disableAbsoluteVolumeControl();
+ localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
+ return;
+ default:
+ return;
+ }
+ }
+
+ private void disableAbsoluteVolumeControl() {
+ if (isPlaybackDevice()) {
+ playback().removeAvcAudioStatusAction();
+ } else if (isTvDevice()) {
+ tv().removeAvcAudioStatusAction();
+ }
+ AudioDeviceAttributes device = getAvcAudioOutputDevice();
+ if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ getAudioManager().setDeviceVolumeBehavior(device,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
+ }
+
+ /**
+ * Enables Absolute Volume Control. Should only be called when all the conditions for
+ * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
+ * @param audioStatus The initial audio status to set the audio output device to
+ */
+ void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
+ HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
+ HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
+ localDevice.findAudioReceiverAddress());
+ VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMuted(audioStatus.getMute())
+ .setVolumeIndex(audioStatus.getVolume())
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build();
+ mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
+ localDevice, systemAudioDevice);
+
+ // AudioService sets the volume of the stream and device based on the input VolumeInfo
+ // when enabling absolute volume behavior, but not the mute state
+ notifyAvcMuteChange(audioStatus.getMute());
+ getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
+ getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener, true);
+ }
+
+ private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
+
+ @VisibleForTesting
+ AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() {
+ return mAbsoluteVolumeChangedListener;
+ }
+
+ /**
+ * Listeners for changes reported by AudioService to the state of an audio output device using
+ * absolute volume behavior.
+ */
+ @VisibleForTesting
+ class AbsoluteVolumeChangedListener implements
+ AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
+ private HdmiCecLocalDevice mLocalDevice;
+ private HdmiDeviceInfo mSystemAudioDevice;
+
+ private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice,
+ HdmiDeviceInfo systemAudioDevice) {
+ mLocalDevice = localDevice;
+ mSystemAudioDevice = systemAudioDevice;
+ }
+
+ /**
+ * Called when AudioService sets the volume level of an absolute volume audio output device
+ * to a numeric value.
+ */
+ @Override
+ public void onAudioDeviceVolumeChanged(
+ @NonNull AudioDeviceAttributes audioDevice,
+ @NonNull VolumeInfo volumeInfo) {
+ int localDeviceAddress;
+ synchronized (mLocalDevice.mLock) {
+ localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
+ }
+ sendCecCommand(SetAudioVolumeLevelMessage.build(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress(),
+ volumeInfo.getVolumeIndex()),
+ // If sending the message fails, ask the System Audio device for its
+ // audio status so that we can update AudioService
+ (int errorCode) -> {
+ if (errorCode == SendMessageResult.SUCCESS) {
+ // Update the volume tracked in our AbsoluteVolumeAudioStatusAction
+ // so it correctly processes incoming <Report Audio Status> messages
+ HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
+ avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
+ } else {
+ sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress()
+ ));
+ }
+ });
+ }
+
+ /**
+ * Called when AudioService adjusts the volume or mute state of an absolute volume
+ * audio output device
+ */
+ @Override
+ public void onAudioDeviceVolumeAdjusted(
+ @NonNull AudioDeviceAttributes audioDevice,
+ @NonNull VolumeInfo volumeInfo,
+ @AudioManager.VolumeAdjustment int direction,
+ @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
+ ) {
+ int keyCode;
+ switch (direction) {
+ case AudioManager.ADJUST_RAISE:
+ keyCode = KeyEvent.KEYCODE_VOLUME_UP;
+ break;
+ case AudioManager.ADJUST_LOWER:
+ keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
+ break;
+ case AudioManager.ADJUST_TOGGLE_MUTE:
+ case AudioManager.ADJUST_MUTE:
+ case AudioManager.ADJUST_UNMUTE:
+ // Many CEC devices only support toggle mute. Therefore, we send the
+ // same keycode for all three mute options.
+ keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
+ break;
+ default:
+ return;
+ }
+ switch (mode) {
+ case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+ mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+ break;
+ case AudioDeviceVolumeManager.ADJUST_MODE_START:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+ break;
+ case AudioDeviceVolumeManager.ADJUST_MODE_END:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ /**
+ * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
+ * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ */
+ void notifyAvcVolumeChange(int volume) {
+ if (!isAbsoluteVolumeControlEnabled()) return;
+ List<AudioDeviceAttributes> streamMusicDevices =
+ getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+ if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
+ }
+ }
+
+ /**
+ * Notifies AudioService of a change in the mute status of the System Audio device. Has no
+ * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ */
+ void notifyAvcMuteChange(boolean mute) {
+ if (!isAbsoluteVolumeControlEnabled()) return;
+ List<AudioDeviceAttributes> streamMusicDevices =
+ getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+ if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
+ getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
+ AudioManager.FLAG_ABSOLUTE_VOLUME);
+ }
+ }
+
+ /**
+ * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index
+ * from HDMI-CEC volume range to STREAM_MUSIC's.
+ */
+ void setStreamMusicVolume(int volume, int flags) {
+ getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
+ volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index adcef66..7daeaf1 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -172,8 +172,19 @@
}
private void sendKeyUp() {
- sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
- mTargetAddress));
+ // When using Absolute Volume Control, query audio status after a volume key is released.
+ // This allows us to notify AudioService of the resulting volume or mute status changes.
+ if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
+ && localDevice().getService().isAbsoluteVolumeControlEnabled()) {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+ mTargetAddress),
+ __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getSourceAddress(),
+ localDevice().findAudioReceiverAddress())));
+ } else {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+ mTargetAddress));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
index 96fd003..eb3b33d 100644
--- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -122,4 +122,11 @@
return true;
}
}
+
+ /**
+ * Returns the logical address of this action's target device.
+ */
+ public int getTargetAddress() {
+ return mTargetAddress;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 978c25d..e7a3db7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -153,7 +153,7 @@
boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
if (receivedStatus == mTargetAudioStatus) {
setSystemAudioMode(receivedStatus);
- startAudioStatusAction();
+ finish();
return true;
} else {
HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
@@ -168,11 +168,6 @@
}
}
- protected void startAudioStatusAction() {
- addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks));
- finish();
- }
-
protected void removeSystemAudioActionInProgress() {
removeActionExcept(SystemAudioActionFromTv.class, this);
removeActionExcept(SystemAudioActionFromAvr.class, this);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 6ddff91..99148c4 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -16,8 +16,8 @@
package com.android.server.hdmi;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
/**
@@ -65,7 +65,7 @@
if (mTargetAudioStatus) {
setSystemAudioMode(true);
- startAudioStatusAction();
+ finish();
} else {
setSystemAudioMode(false);
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
deleted file mode 100644
index b4af540..0000000
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.hdmi;
-
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.tv.cec.V1_0.SendMessageResult;
-
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-
-import java.util.List;
-
-/**
- * Action to update audio status (volume or mute) of audio amplifier
- */
-final class SystemAudioStatusAction extends HdmiCecFeatureAction {
- private static final String TAG = "SystemAudioStatusAction";
-
- // State that waits for <ReportAudioStatus>.
- private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
-
- private final int mAvrAddress;
-
- SystemAudioStatusAction(
- HdmiCecLocalDevice source, int avrAddress, List<IHdmiControlCallback> callbacks) {
- super(source, callbacks);
- mAvrAddress = avrAddress;
- }
-
- SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
- IHdmiControlCallback callback) {
- super(source, callback);
- mAvrAddress = avrAddress;
- }
-
- @Override
- boolean start() {
- mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
- sendGiveAudioStatus();
- return true;
- }
-
- private void sendGiveAudioStatus() {
- sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
- new SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- if (error != SendMessageResult.SUCCESS) {
- handleSendGiveAudioStatusFailure();
- }
- }
- });
- }
-
- private void handleSendGiveAudioStatusFailure() {
-
- // Still return SUCCESS to callback.
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
-
- @Override
- boolean processCommand(HdmiCecMessage cmd) {
- if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) {
- return false;
- }
-
- switch (cmd.getOpcode()) {
- case Constants.MESSAGE_REPORT_AUDIO_STATUS:
- handleReportAudioStatus(cmd);
- return true;
- }
-
- return false;
- }
-
- private void handleReportAudioStatus(HdmiCecMessage cmd) {
- byte[] params = cmd.getParams();
- boolean mute = HdmiUtils.isAudioStatusMute(cmd);
- int volume = HdmiUtils.getAudioStatusVolume(cmd);
- tv().setAudioStatus(mute, volume);
-
- if (!(tv().isSystemAudioActivated() ^ mute)) {
- // Toggle AVR's mute status to match with the system audio status.
- sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE);
- }
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
-
- @Override
- void handleTimerEvent(int state) {
- if (mState != state) {
- return;
- }
-
- handleSendGiveAudioStatusFailure();
- }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7360bbc..603d012 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -73,7 +74,6 @@
import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
-import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -111,6 +111,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
@@ -146,9 +147,7 @@
import java.util.Objects;
import java.util.OptionalInt;
-/*
- * Wraps the C++ InputManager and provides its callbacks.
- */
+/** The system implementation of {@link IInputManager} that manages input devices. */
public class InputManagerService extends IInputManager.Stub
implements Watchdog.Monitor {
static final String TAG = "InputManager";
@@ -182,8 +181,7 @@
/** TODO(b/169067926): Remove this. */
private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
- // Pointer to native input manager service object.
- private final long mPtr;
+ private final NativeInputManagerService mNative;
private final Context mContext;
private final InputManagerHandler mHandler;
@@ -298,92 +296,6 @@
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
- private static native long nativeInit(InputManagerService service,
- Context context, MessageQueue messageQueue);
- private static native void nativeStart(long ptr);
- private static native void nativeSetDisplayViewports(long ptr,
- DisplayViewport[] viewports);
-
- private static native int nativeGetScanCodeState(long ptr,
- int deviceId, int sourceMask, int scanCode);
- private static native int nativeGetKeyCodeState(long ptr,
- int deviceId, int sourceMask, int keyCode);
- private static native int nativeGetSwitchState(long ptr,
- int deviceId, int sourceMask, int sw);
- private static native boolean nativeHasKeys(long ptr,
- int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
- private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId,
- int locationKeyCode);
- private static native InputChannel nativeCreateInputChannel(long ptr, String name);
- private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
- String name, int pid);
- private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken);
- private static native void nativePilferPointers(long ptr, IBinder token);
- private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
- private static native boolean nativeSetInTouchMode(long ptr, boolean inTouchMode, int pid,
- int uid, boolean hasPermission);
- private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
- private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
- private static native int nativeInjectInputEvent(long ptr, InputEvent event,
- int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
- int policyFlags);
- private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
- private static native void nativeToggleCapsLock(long ptr, int deviceId);
- private static native void nativeDisplayRemoved(long ptr, int displayId);
- private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
- private static native void nativeSetSystemUiLightsOut(long ptr, boolean lightsOut);
- private static native void nativeSetFocusedApplication(long ptr,
- int displayId, InputApplicationHandle application);
- private static native void nativeSetFocusedDisplay(long ptr, int displayId);
- private static native boolean nativeTransferTouchFocus(long ptr,
- IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
- private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
- private static native void nativeSetPointerSpeed(long ptr, int speed);
- private static native void nativeSetPointerAcceleration(long ptr, float acceleration);
- private static native void nativeSetShowTouches(long ptr, boolean enabled);
- private static native void nativeSetInteractive(long ptr, boolean interactive);
- private static native void nativeReloadCalibration(long ptr);
- private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
- int[] amplitudes, int repeat, int token);
- private static native void nativeVibrateCombined(long ptr, int deviceId, long[] pattern,
- SparseArray<int[]> amplitudes, int repeat, int token);
- private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
- private static native boolean nativeIsVibrating(long ptr, int deviceId);
- private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
- private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
- private static native int nativeGetBatteryStatus(long ptr, int deviceId);
- private static native List<Light> nativeGetLights(long ptr, int deviceId);
- private static native int nativeGetLightPlayerId(long ptr, int deviceId, int lightId);
- private static native int nativeGetLightColor(long ptr, int deviceId, int lightId);
- private static native void nativeSetLightPlayerId(long ptr, int deviceId, int lightId,
- int playerId);
- private static native void nativeSetLightColor(long ptr, int deviceId, int lightId, int color);
- private static native void nativeReloadKeyboardLayouts(long ptr);
- private static native void nativeReloadDeviceAliases(long ptr);
- private static native String nativeDump(long ptr);
- private static native void nativeMonitor(long ptr);
- private static native boolean nativeIsInputDeviceEnabled(long ptr, int deviceId);
- private static native void nativeEnableInputDevice(long ptr, int deviceId);
- private static native void nativeDisableInputDevice(long ptr, int deviceId);
- private static native void nativeSetPointerIconType(long ptr, int iconId);
- private static native void nativeReloadPointerIcons(long ptr);
- private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
- private static native void nativeRequestPointerCapture(long ptr, IBinder windowToken,
- boolean enabled);
- private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
- private static native void nativeNotifyPortAssociationsChanged(long ptr);
- private static native void nativeChangeUniqueIdAssociation(long ptr);
- private static native void nativeNotifyPointerDisplayIdChanged(long ptr);
- private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId,
- boolean enabled);
- private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
- private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId);
- private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType);
- private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType,
- int samplingPeriodUs, int maxBatchReportLatencyUs);
- private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType);
- private static native void nativeCancelCurrentTouch(long ptr);
-
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -450,18 +362,47 @@
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
final boolean mUseDevInputEventForAudioJack;
+ /** Point of injection for test dependencies. */
+ @VisibleForTesting
+ static class Injector {
+ private final Context mContext;
+ private final Looper mLooper;
+
+ Injector(Context context, Looper looper) {
+ mContext = context;
+ mLooper = looper;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ Looper getLooper() {
+ return mLooper;
+ }
+
+ NativeInputManagerService getNativeService(InputManagerService service) {
+ return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
+ }
+ }
+
public InputManagerService(Context context) {
- this.mContext = context;
- this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
+ this(new Injector(context, DisplayThread.get().getLooper()));
+ }
+
+ @VisibleForTesting
+ InputManagerService(Injector injector) {
+ mContext = injector.getContext();
+ mHandler = new InputManagerHandler(injector.getLooper());
+ mNative = injector.getNativeService(this);
mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
- context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
+ mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
- String doubleTouchGestureEnablePath = context.getResources().getString(
+ String doubleTouchGestureEnablePath = mContext.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
@@ -504,9 +445,9 @@
public void start() {
Slog.i(TAG, "Starting input manager");
- nativeStart(mPtr);
+ mNative.start();
- // Add ourself to the Watchdog monitors.
+ // Add ourselves to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
registerPointerSpeedSettingObserver();
@@ -599,14 +540,14 @@
if (DEBUG) {
Slog.d(TAG, "Reloading keyboard layouts.");
}
- nativeReloadKeyboardLayouts(mPtr);
+ mNative.reloadKeyboardLayouts();
}
private void reloadDeviceAliases() {
if (DEBUG) {
Slog.d(TAG, "Reloading device names.");
}
- nativeReloadDeviceAliases(mPtr);
+ mNative.reloadDeviceAliases();
}
private void setDisplayViewportsInternal(List<DisplayViewport> viewports) {
@@ -615,7 +556,7 @@
for (int i = viewports.size() - 1; i >= 0; --i) {
vArray[i] = viewports.get(i);
}
- nativeSetDisplayViewports(mPtr, vArray);
+ mNative.setDisplayViewports(vArray);
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
final AdditionalDisplayInputProperties properties =
@@ -642,7 +583,7 @@
* @return The key state.
*/
public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) {
- return nativeGetKeyCodeState(mPtr, deviceId, sourceMask, keyCode);
+ return mNative.getKeyCodeState(deviceId, sourceMask, keyCode);
}
/**
@@ -655,7 +596,7 @@
* @return The key state.
*/
public int getScanCodeState(int deviceId, int sourceMask, int scanCode) {
- return nativeGetScanCodeState(mPtr, deviceId, sourceMask, scanCode);
+ return mNative.getScanCodeState(deviceId, sourceMask, scanCode);
}
/**
@@ -668,7 +609,7 @@
* @return The switch state.
*/
public int getSwitchState(int deviceId, int sourceMask, int switchCode) {
- return nativeGetSwitchState(mPtr, deviceId, sourceMask, switchCode);
+ return mNative.getSwitchState(deviceId, sourceMask, switchCode);
}
/**
@@ -691,7 +632,7 @@
throw new IllegalArgumentException("keyExists must be at least as large as keyCodes");
}
- return nativeHasKeys(mPtr, deviceId, sourceMask, keyCodes, keyExists);
+ return mNative.hasKeys(deviceId, sourceMask, keyCodes, keyExists);
}
/**
@@ -707,7 +648,7 @@
if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) {
return KEYCODE_UNKNOWN;
}
- return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode);
+ return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
}
/**
@@ -720,7 +661,7 @@
public boolean transferTouch(IBinder destChannelToken) {
// TODO(b/162194035): Replace this with a SPY window
Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
- return nativeTransferTouch(mPtr, destChannelToken);
+ return mNative.transferTouch(destChannelToken);
}
/**
@@ -736,7 +677,7 @@
throw new IllegalArgumentException("displayId must >= 0.");
}
- return nativeCreateInputMonitor(mPtr, displayId, inputChannelName, Binder.getCallingPid());
+ return mNative.createInputMonitor(displayId, inputChannelName, Binder.getCallingPid());
}
@NonNull
@@ -818,7 +759,7 @@
* @param name The name of this input channel
*/
public InputChannel createInputChannel(String name) {
- return nativeCreateInputChannel(mPtr, name);
+ return mNative.createInputChannel(name);
}
/**
@@ -827,7 +768,7 @@
*/
public void removeInputChannel(IBinder connectionToken) {
Objects.requireNonNull(connectionToken, "connectionToken must not be null");
- nativeRemoveInputChannel(mPtr, connectionToken);
+ mNative.removeInputChannel(connectionToken);
}
/**
@@ -869,7 +810,7 @@
}
}
- nativeSetInputFilterEnabled(mPtr, filter != null);
+ mNative.setInputFilterEnabled(filter != null);
}
}
@@ -893,11 +834,19 @@
* @return {@code true} if the touch mode was successfully changed, {@code false} otherwise
*/
public boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission) {
- return nativeSetInTouchMode(mPtr, inTouchMode, pid, uid, hasPermission);
+ return mNative.setInTouchMode(inTouchMode, pid, uid, hasPermission);
}
@Override // Binder call
- public boolean injectInputEvent(InputEvent event, int mode) {
+ public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "injectInputEvent()", true /*checkInstrumentationSource*/)) {
+ throw new SecurityException(
+ "Injecting input events requires the caller (or the source of the "
+ + "instrumentation, if any) to have the INJECT_EVENTS permission.");
+ }
+ // We are not checking if targetUid matches the callingUid, since having the permission
+ // already means you can inject into any window.
Objects.requireNonNull(event, "event must not be null");
if (mode != InputEventInjectionSync.NONE
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -906,22 +855,41 @@
}
final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
+ final boolean injectIntoUid = targetUid != Process.INVALID_UID;
final int result;
try {
- result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
- INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ result = mNative.injectInputEvent(event, injectIntoUid,
+ targetUid, mode, INJECTION_TIMEOUT_MILLIS,
+ WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
} finally {
Binder.restoreCallingIdentity(ident);
}
switch (result) {
- case InputEventInjectionResult.PERMISSION_DENIED:
- Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
- throw new SecurityException(
- "Injecting to another application requires INJECT_EVENTS permission");
case InputEventInjectionResult.SUCCEEDED:
return true;
+ case InputEventInjectionResult.TARGET_MISMATCH:
+ if (!injectIntoUid) {
+ throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
+ + " when it is not targeted into to a specific uid.");
+ }
+ // Attempt to inject into a window owned by the instrumentation source of the caller
+ // because it is possible that tests adopt the identity of the shell when launching
+ // activities that they would like to inject into.
+ final ActivityManagerInternal ami =
+ LocalServices.getService(ActivityManagerInternal.class);
+ Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+ final int instrUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+ if (instrUid != Process.INVALID_UID && targetUid != instrUid) {
+ Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
+ + targetUid + ". Attempting to inject into window owned by "
+ + "instrumentation source uid " + instrUid + ".");
+ return injectInputEvent(event, mode, instrUid);
+ }
+ throw new IllegalArgumentException(
+ "Targeted input event injection from pid " + pid
+ + " was not directed at a window owned by uid "
+ + targetUid + ".");
case InputEventInjectionResult.TIMED_OUT:
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
return false;
@@ -935,7 +903,7 @@
@Override // Binder call
public VerifiedInputEvent verifyInputEvent(InputEvent event) {
Objects.requireNonNull(event, "event must not be null");
- return nativeVerifyInputEvent(mPtr, event);
+ return mNative.verifyInputEvent(event);
}
/**
@@ -958,7 +926,7 @@
// Binder call
@Override
public boolean isInputDeviceEnabled(int deviceId) {
- return nativeIsInputDeviceEnabled(mPtr, deviceId);
+ return mNative.isInputDeviceEnabled(deviceId);
}
// Binder call
@@ -968,7 +936,7 @@
"enableInputDevice()")) {
throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
}
- nativeEnableInputDevice(mPtr, deviceId);
+ mNative.enableInputDevice(deviceId);
}
// Binder call
@@ -978,7 +946,7 @@
"disableInputDevice()")) {
throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
}
- nativeDisableInputDevice(mPtr, deviceId);
+ mNative.disableInputDevice(deviceId);
}
/**
@@ -1224,7 +1192,7 @@
try {
if (mDataStore.setTouchCalibration(inputDeviceDescriptor, surfaceRotation,
calibration)) {
- nativeReloadCalibration(mPtr);
+ mNative.reloadCalibration();
}
} finally {
mDataStore.saveIfNeeded();
@@ -1736,11 +1704,11 @@
}
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
- nativeSetFocusedApplication(mPtr, displayId, application);
+ mNative.setFocusedApplication(displayId, application);
}
public void setFocusedDisplay(int displayId) {
- nativeSetFocusedDisplay(mPtr, displayId);
+ mNative.setFocusedDisplay(displayId);
}
/** Clean up input window handles of the given display. */
@@ -1750,22 +1718,22 @@
mPointerIconDisplayContext = null;
}
- nativeDisplayRemoved(mPtr, displayId);
+ mNative.displayRemoved(displayId);
}
@Override
public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) {
Objects.requireNonNull(inputChannelToken, "event must not be null");
- nativeRequestPointerCapture(mPtr, inputChannelToken, enabled);
+ mNative.requestPointerCapture(inputChannelToken, enabled);
}
public void setInputDispatchMode(boolean enabled, boolean frozen) {
- nativeSetInputDispatchMode(mPtr, enabled, frozen);
+ mNative.setInputDispatchMode(enabled, frozen);
}
public void setSystemUiLightsOut(boolean lightsOut) {
- nativeSetSystemUiLightsOut(mPtr, lightsOut);
+ mNative.setSystemUiLightsOut(lightsOut);
}
/**
@@ -1784,7 +1752,7 @@
*/
public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
@NonNull InputChannel toChannel, boolean isDragDrop) {
- return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken(),
+ return mNative.transferTouchFocus(fromChannel.getToken(), toChannel.getToken(),
isDragDrop);
}
@@ -1805,7 +1773,7 @@
@NonNull IBinder toChannelToken) {
Objects.nonNull(fromChannelToken);
Objects.nonNull(toChannelToken);
- return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken,
+ return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -1831,7 +1799,7 @@
private void setPointerSpeedUnchecked(int speed) {
speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
InputManager.MAX_POINTER_SPEED);
- nativeSetPointerSpeed(mPtr, speed);
+ mNative.setPointerSpeed(speed);
}
private void setPointerAcceleration(float acceleration, int displayId) {
@@ -1854,7 +1822,7 @@
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private void updatePointerAccelerationLocked(float acceleration) {
- nativeSetPointerAcceleration(mPtr, acceleration);
+ mNative.setPointerAcceleration(acceleration);
}
private void setPointerIconVisible(boolean visible, int displayId) {
@@ -1879,12 +1847,12 @@
private void updatePointerIconVisibleLocked(boolean visible) {
if (visible) {
if (mIconType == PointerIcon.TYPE_CUSTOM) {
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
} else {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
} else {
- nativeSetPointerIconType(mPtr, PointerIcon.TYPE_NULL);
+ mNative.setPointerIconType(PointerIcon.TYPE_NULL);
}
}
@@ -1911,7 +1879,7 @@
private void updateShowTouchesFromSettings() {
int setting = getShowTouchesSetting(0);
- nativeSetShowTouches(mPtr, setting != 0);
+ mNative.setShowTouches(setting != 0);
}
private void registerShowTouchesSettingObserver() {
@@ -1930,7 +1898,7 @@
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
0, UserHandle.USER_CURRENT);
PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
- nativeReloadPointerIcons(mPtr);
+ mNative.reloadPointerIcons();
}
private void registerAccessibilityLargePointerSettingObserver() {
@@ -1958,7 +1926,7 @@
(enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
+ ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
+ ", long press timeout = " + timeout);
- nativeSetMotionClassifierEnabled(mPtr, enabled);
+ mNative.setMotionClassifierEnabled(enabled);
}
private void registerLongPressTimeoutObserver() {
@@ -1986,7 +1954,7 @@
private void updateBlockUntrustedTouchesModeFromSettings() {
final int mode = InputManager.getInstance().getBlockUntrustedTouchesMode(mContext);
- nativeSetBlockUntrustedTouchesMode(mPtr, mode);
+ mNative.setBlockUntrustedTouchesMode(mode);
}
private void registerMaximumObscuringOpacityForTouchSettingObserver() {
@@ -2008,7 +1976,7 @@
+ ", it should be >= 0 and <= 1, rejecting update.");
return;
}
- nativeSetMaximumObscuringOpacityForTouch(mPtr, opacity);
+ mNative.setMaximumObscuringOpacityForTouch(opacity);
}
private int getShowTouchesSetting(int defaultValue) {
@@ -2034,7 +2002,7 @@
}
}
// TODO(b/215597605): trigger MousePositionTracker update
- nativeNotifyPointerDisplayIdChanged(mPtr);
+ mNative.notifyPointerDisplayIdChanged();
}
private int getVirtualMousePointerDisplayId() {
@@ -2044,7 +2012,7 @@
}
private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
- nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible);
+ mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
private static class VibrationInfo {
@@ -2143,7 +2111,7 @@
VibratorToken v = getVibratorToken(deviceId, token);
synchronized (v) {
v.mVibrating = true;
- nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
info.getRepeatIndex(), v.mTokenValue);
}
}
@@ -2151,13 +2119,13 @@
// Binder call
@Override
public int[] getVibratorIds(int deviceId) {
- return nativeGetVibratorIds(mPtr, deviceId);
+ return mNative.getVibratorIds(deviceId);
}
// Binder call
@Override
public boolean isVibrating(int deviceId) {
- return nativeIsVibrating(mPtr, deviceId);
+ return mNative.isVibrating(deviceId);
}
// Binder call
@@ -2175,7 +2143,7 @@
if (effect instanceof CombinedVibration.Mono) {
CombinedVibration.Mono mono = (CombinedVibration.Mono) effect;
VibrationInfo info = new VibrationInfo(mono.getEffect());
- nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
info.getRepeatIndex(), v.mTokenValue);
} else if (effect instanceof CombinedVibration.Stereo) {
CombinedVibration.Stereo stereo = (CombinedVibration.Stereo) effect;
@@ -2194,7 +2162,7 @@
}
amplitudes.put(effects.keyAt(i), info.getAmplitudes());
}
- nativeVibrateCombined(mPtr, deviceId, pattern, amplitudes, repeat,
+ mNative.vibrateCombined(deviceId, pattern, amplitudes, repeat,
v.mTokenValue);
}
}
@@ -2225,7 +2193,7 @@
private void cancelVibrateIfNeeded(VibratorToken v) {
synchronized (v) {
if (v.mVibrating) {
- nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+ mNative.cancelVibrate(v.mDeviceId, v.mTokenValue);
v.mVibrating = false;
}
}
@@ -2321,13 +2289,13 @@
// Binder call
@Override
public int getBatteryStatus(int deviceId) {
- return nativeGetBatteryStatus(mPtr, deviceId);
+ return mNative.getBatteryStatus(deviceId);
}
// Binder call
@Override
public int getBatteryCapacity(int deviceId) {
- return nativeGetBatteryCapacity(mPtr, deviceId);
+ return mNative.getBatteryCapacity(deviceId);
}
// Binder call
@@ -2343,10 +2311,10 @@
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId);
if (properties == null || properties.pointerIconVisible) {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
} else {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
}
}
@@ -2364,10 +2332,10 @@
if (properties == null || properties.pointerIconVisible) {
// Only set the icon if it is not currently hidden; otherwise, it will be set
// once it's no longer hidden.
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
}
} else {
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
}
}
}
@@ -2391,7 +2359,7 @@
synchronized (mAssociationsLock) {
mRuntimeAssociations.put(inputPort, displayPort);
}
- nativeNotifyPortAssociationsChanged(mPtr);
+ mNative.notifyPortAssociationsChanged();
}
/**
@@ -2412,7 +2380,7 @@
synchronized (mAssociationsLock) {
mRuntimeAssociations.remove(inputPort);
}
- nativeNotifyPortAssociationsChanged(mPtr);
+ mNative.notifyPortAssociationsChanged();
}
@Override // Binder call
@@ -2429,7 +2397,7 @@
synchronized (mAssociationsLock) {
mUniqueIdAssociations.put(inputPort, displayUniqueId);
}
- nativeChangeUniqueIdAssociation(mPtr);
+ mNative.changeUniqueIdAssociation();
}
@Override // Binder call
@@ -2445,12 +2413,12 @@
synchronized (mAssociationsLock) {
mUniqueIdAssociations.remove(inputPort);
}
- nativeChangeUniqueIdAssociation(mPtr);
+ mNative.changeUniqueIdAssociation();
}
@Override // Binder call
public InputSensorInfo[] getSensorList(int deviceId) {
- return nativeGetSensorList(mPtr, deviceId);
+ return mNative.getSensorList(deviceId);
}
@Override // Binder call
@@ -2511,7 +2479,7 @@
int callingPid = Binder.getCallingPid();
SensorEventListenerRecord listener = mSensorEventListeners.get(callingPid);
if (listener != null) {
- return nativeFlushSensor(mPtr, deviceId, sensorType);
+ return mNative.flushSensor(deviceId, sensorType);
}
return false;
}
@@ -2521,7 +2489,7 @@
public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
int maxBatchReportLatencyUs) {
synchronized (mInputDevicesLock) {
- return nativeEnableSensor(mPtr, deviceId, sensorType, samplingPeriodUs,
+ return mNative.enableSensor(deviceId, sensorType, samplingPeriodUs,
maxBatchReportLatencyUs);
}
}
@@ -2529,7 +2497,7 @@
@Override // Binder call
public void disableSensor(int deviceId, int sensorType) {
synchronized (mInputDevicesLock) {
- nativeDisableSensor(mPtr, deviceId, sensorType);
+ mNative.disableSensor(deviceId, sensorType);
}
}
@@ -2568,7 +2536,7 @@
*/
@Override // Binder call
public List<Light> getLights(int deviceId) {
- return nativeGetLights(mPtr, deviceId);
+ return mNative.getLights(deviceId);
}
/**
@@ -2581,11 +2549,11 @@
+ "lightState " + lightState);
}
if (light.getType() == Light.LIGHT_TYPE_PLAYER_ID) {
- nativeSetLightPlayerId(mPtr, deviceId, light.getId(), lightState.getPlayerId());
+ mNative.setLightPlayerId(deviceId, light.getId(), lightState.getPlayerId());
} else {
// Set ARGB format color to input device light
// Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
- nativeSetLightColor(mPtr, deviceId, light.getId(), lightState.getColor());
+ mNative.setLightColor(deviceId, light.getId(), lightState.getColor());
}
}
@@ -2593,7 +2561,7 @@
* Set multiple light states with multiple light ids for a specific input device.
*/
private void setLightStatesInternal(int deviceId, int[] lightIds, LightState[] lightStates) {
- final List<Light> lights = nativeGetLights(mPtr, deviceId);
+ final List<Light> lights = mNative.getLights(deviceId);
SparseArray<Light> lightArray = new SparseArray<>();
for (int i = 0; i < lights.size(); i++) {
lightArray.put(lights.get(i).getId(), lights.get(i));
@@ -2629,8 +2597,8 @@
@Override
public @Nullable LightState getLightState(int deviceId, int lightId) {
synchronized (mLightLock) {
- int color = nativeGetLightColor(mPtr, deviceId, lightId);
- int playerId = nativeGetLightPlayerId(mPtr, deviceId, lightId);
+ int color = mNative.getLightColor(deviceId, lightId);
+ int playerId = mNative.getLightPlayerId(deviceId, lightId);
return new LightState(color, playerId);
}
@@ -2682,7 +2650,7 @@
throw new SecurityException("Requires MONITOR_INPUT permission");
}
- nativeCancelCurrentTouch(mPtr);
+ mNative.cancelCurrentTouch();
}
@Override
@@ -2690,7 +2658,7 @@
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
pw.println("INPUT MANAGER (dumpsys input)\n");
- String dumpStr = nativeDump(mPtr);
+ String dumpStr = mNative.dump();
if (dumpStr != null) {
pw.println(dumpStr);
}
@@ -2757,8 +2725,12 @@
}
}
}
-
private boolean checkCallingPermission(String permission, String func) {
+ return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
+ }
+
+ private boolean checkCallingPermission(String permission, String func,
+ boolean checkInstrumentationSource) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -2767,6 +2739,18 @@
if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
return true;
}
+
+ if (checkInstrumentationSource) {
+ final ActivityManagerInternal ami =
+ LocalServices.getService(ActivityManagerInternal.class);
+ Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+ final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+ if (instrumentationUid != Process.INVALID_UID && mContext.checkPermission(permission,
+ -1 /*pid*/, instrumentationUid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
@@ -2783,7 +2767,7 @@
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
- nativeMonitor(mPtr);
+ mNative.monitor();
}
// Native callback.
@@ -3012,13 +2996,6 @@
// Native callback.
@SuppressWarnings("unused")
- private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
- return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
- injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void onPointerDownOutsideFocus(IBinder touchedToken) {
mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
}
@@ -3117,7 +3094,7 @@
* @return True if the device could dispatch to the given display, false otherwise.
*/
public boolean canDispatchToDisplay(int deviceId, int displayId) {
- return nativeCanDispatchToDisplay(mPtr, deviceId, displayId);
+ return mNative.canDispatchToDisplay(deviceId, displayId);
}
// Native callback.
@@ -3459,12 +3436,17 @@
@Override
public void sendInputEvent(InputEvent event, int policyFlags) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "sendInputEvent()")) {
+ throw new SecurityException(
+ "The INJECT_EVENTS permission is required for injecting input events.");
+ }
Objects.requireNonNull(event, "event must not be null");
synchronized (mInputFilterLock) {
if (!mDisconnected) {
- nativeInjectInputEvent(mPtr, event, 0, 0,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+ mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
}
}
@@ -3483,7 +3465,7 @@
@Override
public void pilferPointers() {
- nativePilferPointers(mPtr, mInputChannelToken);
+ mNative.pilferPointers(mInputChannelToken);
}
@Override
@@ -3661,12 +3643,12 @@
@Override
public void setInteractive(boolean interactive) {
- nativeSetInteractive(mPtr, interactive);
+ mNative.setInteractive(interactive);
}
@Override
public void toggleCapsLock(int deviceId) {
- nativeToggleCapsLock(mPtr, deviceId);
+ mNative.toggleCapsLock(deviceId);
}
@Override
@@ -3737,7 +3719,7 @@
@Override
public void pilferPointers(IBinder token) {
- nativePilferPointers(mPtr, token);
+ mNative.pilferPointers(token);
}
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
new file mode 100644
index 0000000..7178d20
--- /dev/null
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.content.Context;
+import android.hardware.display.DisplayViewport;
+import android.hardware.input.InputSensorInfo;
+import android.hardware.lights.Light;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.util.SparseArray;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.VerifiedInputEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * An interface for the native methods of InputManagerService. We use a public interface so that
+ * this can be mocked for testing by Mockito.
+ */
+@VisibleForTesting
+public interface NativeInputManagerService {
+
+ void start();
+
+ void setDisplayViewports(DisplayViewport[] viewports);
+
+ int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+ int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+ int getSwitchState(int deviceId, int sourceMask, int sw);
+
+ boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+
+ int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+ InputChannel createInputChannel(String name);
+
+ InputChannel createInputMonitor(int displayId, String name, int pid);
+
+ void removeInputChannel(IBinder connectionToken);
+
+ void pilferPointers(IBinder token);
+
+ void setInputFilterEnabled(boolean enable);
+
+ boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission);
+
+ void setMaximumObscuringOpacityForTouch(float opacity);
+
+ void setBlockUntrustedTouchesMode(int mode);
+
+ int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode,
+ int timeoutMillis, int policyFlags);
+
+ VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+ void toggleCapsLock(int deviceId);
+
+ void displayRemoved(int displayId);
+
+ void setInputDispatchMode(boolean enabled, boolean frozen);
+
+ void setSystemUiLightsOut(boolean lightsOut);
+
+ void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+ void setFocusedDisplay(int displayId);
+
+ boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean isDragDrop);
+
+ boolean transferTouch(IBinder destChannelToken);
+
+ void setPointerSpeed(int speed);
+
+ void setPointerAcceleration(float acceleration);
+
+ void setShowTouches(boolean enabled);
+
+ void setInteractive(boolean interactive);
+
+ void reloadCalibration();
+
+ void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat, int token);
+
+ void vibrateCombined(int deviceId, long[] pattern, SparseArray<int[]> amplitudes,
+ int repeat, int token);
+
+ void cancelVibrate(int deviceId, int token);
+
+ boolean isVibrating(int deviceId);
+
+ int[] getVibratorIds(int deviceId);
+
+ int getBatteryCapacity(int deviceId);
+
+ int getBatteryStatus(int deviceId);
+
+ List<Light> getLights(int deviceId);
+
+ int getLightPlayerId(int deviceId, int lightId);
+
+ int getLightColor(int deviceId, int lightId);
+
+ void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+ void setLightColor(int deviceId, int lightId, int color);
+
+ void reloadKeyboardLayouts();
+
+ void reloadDeviceAliases();
+
+ String dump();
+
+ void monitor();
+
+ boolean isInputDeviceEnabled(int deviceId);
+
+ void enableInputDevice(int deviceId);
+
+ void disableInputDevice(int deviceId);
+
+ void setPointerIconType(int iconId);
+
+ void reloadPointerIcons();
+
+ void setCustomPointerIcon(PointerIcon icon);
+
+ void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+ boolean canDispatchToDisplay(int deviceId, int displayId);
+
+ void notifyPortAssociationsChanged();
+
+ void changeUniqueIdAssociation();
+
+ void notifyPointerDisplayIdChanged();
+
+ void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+ void setMotionClassifierEnabled(boolean enabled);
+
+ InputSensorInfo[] getSensorList(int deviceId);
+
+ boolean flushSensor(int deviceId, int sensorType);
+
+ boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+ int maxBatchReportLatencyUs);
+
+ void disableSensor(int deviceId, int sensorType);
+
+ void cancelCurrentTouch();
+
+ /** The native implementation of InputManagerService methods. */
+ class NativeImpl implements NativeInputManagerService {
+ /** Pointer to native input manager service object, used by native code. */
+ @SuppressWarnings({"unused", "FieldCanBeLocal"})
+ private final long mPtr;
+
+ NativeImpl(InputManagerService service, Context context, MessageQueue messageQueue) {
+ mPtr = init(service, context, messageQueue);
+ }
+
+ private native long init(InputManagerService service, Context context,
+ MessageQueue messageQueue);
+
+ @Override
+ public native void start();
+
+ @Override
+ public native void setDisplayViewports(DisplayViewport[] viewports);
+
+ @Override
+ public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+ @Override
+ public native int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+ @Override
+ public native int getSwitchState(int deviceId, int sourceMask, int sw);
+
+ @Override
+ public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
+ boolean[] keyExists);
+
+ @Override
+ public native int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+ @Override
+ public native InputChannel createInputChannel(String name);
+
+ @Override
+ public native InputChannel createInputMonitor(int displayId, String name, int pid);
+
+ @Override
+ public native void removeInputChannel(IBinder connectionToken);
+
+ @Override
+ public native void pilferPointers(IBinder token);
+
+ @Override
+ public native void setInputFilterEnabled(boolean enable);
+
+ @Override
+ public native boolean setInTouchMode(boolean inTouchMode, int pid, int uid,
+ boolean hasPermission);
+
+ @Override
+ public native void setMaximumObscuringOpacityForTouch(float opacity);
+
+ @Override
+ public native void setBlockUntrustedTouchesMode(int mode);
+
+ @Override
+ public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid,
+ int syncMode,
+ int timeoutMillis, int policyFlags);
+
+ @Override
+ public native VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+ @Override
+ public native void toggleCapsLock(int deviceId);
+
+ @Override
+ public native void displayRemoved(int displayId);
+
+ @Override
+ public native void setInputDispatchMode(boolean enabled, boolean frozen);
+
+ @Override
+ public native void setSystemUiLightsOut(boolean lightsOut);
+
+ @Override
+ public native void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+ @Override
+ public native void setFocusedDisplay(int displayId);
+
+ @Override
+ public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean isDragDrop);
+
+ @Override
+ public native boolean transferTouch(IBinder destChannelToken);
+
+ @Override
+ public native void setPointerSpeed(int speed);
+
+ @Override
+ public native void setPointerAcceleration(float acceleration);
+
+ @Override
+ public native void setShowTouches(boolean enabled);
+
+ @Override
+ public native void setInteractive(boolean interactive);
+
+ @Override
+ public native void reloadCalibration();
+
+ @Override
+ public native void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat,
+ int token);
+
+ @Override
+ public native void vibrateCombined(int deviceId, long[] pattern,
+ SparseArray<int[]> amplitudes,
+ int repeat, int token);
+
+ @Override
+ public native void cancelVibrate(int deviceId, int token);
+
+ @Override
+ public native boolean isVibrating(int deviceId);
+
+ @Override
+ public native int[] getVibratorIds(int deviceId);
+
+ @Override
+ public native int getBatteryCapacity(int deviceId);
+
+ @Override
+ public native int getBatteryStatus(int deviceId);
+
+ @Override
+ public native List<Light> getLights(int deviceId);
+
+ @Override
+ public native int getLightPlayerId(int deviceId, int lightId);
+
+ @Override
+ public native int getLightColor(int deviceId, int lightId);
+
+ @Override
+ public native void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+ @Override
+ public native void setLightColor(int deviceId, int lightId, int color);
+
+ @Override
+ public native void reloadKeyboardLayouts();
+
+ @Override
+ public native void reloadDeviceAliases();
+
+ @Override
+ public native String dump();
+
+ @Override
+ public native void monitor();
+
+ @Override
+ public native boolean isInputDeviceEnabled(int deviceId);
+
+ @Override
+ public native void enableInputDevice(int deviceId);
+
+ @Override
+ public native void disableInputDevice(int deviceId);
+
+ @Override
+ public native void setPointerIconType(int iconId);
+
+ @Override
+ public native void reloadPointerIcons();
+
+ @Override
+ public native void setCustomPointerIcon(PointerIcon icon);
+
+ @Override
+ public native void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+ @Override
+ public native boolean canDispatchToDisplay(int deviceId, int displayId);
+
+ @Override
+ public native void notifyPortAssociationsChanged();
+
+ @Override
+ public native void changeUniqueIdAssociation();
+
+ @Override
+ public native void notifyPointerDisplayIdChanged();
+
+ @Override
+ public native void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+ @Override
+ public native void setMotionClassifierEnabled(boolean enabled);
+
+ @Override
+ public native InputSensorInfo[] getSensorList(int deviceId);
+
+ @Override
+ public native boolean flushSensor(int deviceId, int sensorType);
+
+ @Override
+ public native boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+ int maxBatchReportLatencyUs);
+
+ @Override
+ public native void disableSensor(int deviceId, int sensorType);
+
+ @Override
+ public native void cancelCurrentTouch();
+ }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index db81393..37a4869 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -30,8 +30,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.RemoteException;
@@ -78,7 +76,7 @@
private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
private final LocaleManagerService mLocaleManagerService;
- private final PackageManagerInternal mPackageManagerInternal;
+ private final PackageManager mPackageManager;
private final Clock mClock;
private final Context mContext;
private final Object mStagedDataLock = new Object();
@@ -90,18 +88,18 @@
private final BroadcastReceiver mUserMonitor;
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, HandlerThread broadcastHandlerThread) {
- this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
+ PackageManager packageManager, HandlerThread broadcastHandlerThread) {
+ this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
new SparseArray<>(), broadcastHandlerThread);
}
@VisibleForTesting LocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData,
+ PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
HandlerThread broadcastHandlerThread) {
mContext = context;
mLocaleManagerService = localeManagerService;
- mPackageManagerInternal = pmInternal;
+ mPackageManager = packageManager;
mClock = clock;
mStagedData = stagedData;
@@ -130,8 +128,8 @@
}
HashMap<String, String> pkgStates = new HashMap<>();
- for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
- userId, Binder.getCallingUid())) {
+ for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
+ PackageManager.ApplicationInfoFlags.of(0), userId)) {
try {
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
appInfo.packageName,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 924db6a..9ef14cc 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -28,7 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.HandlerThread;
@@ -60,7 +60,7 @@
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
- private PackageManagerInternal mPackageManagerInternal;
+ private PackageManager mPackageManager;
private LocaleManagerBackupHelper mBackupHelper;
@@ -74,7 +74,7 @@
mBinderService = new LocaleManagerBinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mPackageManager = mContext.getPackageManager();
HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND);
@@ -90,7 +90,7 @@
});
mBackupHelper = new LocaleManagerBackupHelper(this,
- mPackageManagerInternal, broadcastHandlerThread);
+ mPackageManager, broadcastHandlerThread);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
systemAppUpdateTracker);
@@ -102,7 +102,7 @@
@VisibleForTesting
LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
ActivityManagerInternal activityManagerInternal,
- PackageManagerInternal packageManagerInternal,
+ PackageManager packageManager,
LocaleManagerBackupHelper localeManagerBackupHelper,
PackageMonitor packageMonitor) {
super(context);
@@ -110,7 +110,7 @@
mBinderService = new LocaleManagerBinderService();
mActivityTaskManagerInternal = activityTaskManagerInternal;
mActivityManagerInternal = activityManagerInternal;
- mPackageManagerInternal = packageManagerInternal;
+ mPackageManager = packageManager;
mBackupHelper = localeManagerBackupHelper;
mPackageMonitor = packageMonitor;
}
@@ -419,8 +419,12 @@
}
private int getPackageUid(String appPackageName, int userId) {
- return mPackageManagerInternal
- .getPackageUid(appPackageName, /* flags */ 0, userId);
+ try {
+ return mPackageManager
+ .getPackageUidAsUser(appPackageName, PackageInfoFlags.of(0), userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ }
}
@Nullable
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index fac5106..31d5136 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -94,7 +94,6 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
@@ -279,6 +278,9 @@
this::onLocationUserSettingsChanged);
mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
this::onLocationModeChanged);
+ mInjector.getSettingsHelper().addAdasAllowlistChangedListener(
+ () -> refreshAppOpsRestrictions(UserHandle.USER_ALL)
+ );
mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener(
() -> refreshAppOpsRestrictions(UserHandle.USER_ALL));
mInjector.getUserInfoHelper().addListener((userId, change) -> {
@@ -823,12 +825,6 @@
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
- if (!ArrayUtils.contains(mContext.getResources().getStringArray(
- com.android.internal.R.array.config_locationDriverAssistancePackageNames),
- identity.getPackageName())) {
- throw new SecurityException(
- "only verified adas packages may use adas gnss bypass requests");
- }
if (!isLocationProvider) {
LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
@@ -923,12 +919,6 @@
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
- if (!ArrayUtils.contains(mContext.getResources().getStringArray(
- com.android.internal.R.array.config_locationDriverAssistancePackageNames),
- identity.getPackageName())) {
- throw new SecurityException(
- "only verified adas packages may use adas gnss bypass requests");
- }
if (!isLocationProvider) {
LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
@@ -1542,6 +1532,7 @@
}
}
builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
+ builder.add(mInjector.getSettingsHelper().getAdasAllowlist());
allowedPackages = builder.build();
}
diff --git a/services/core/java/com/android/server/location/injector/SettingsHelper.java b/services/core/java/com/android/server/location/injector/SettingsHelper.java
index 148afa7..490bfe1 100644
--- a/services/core/java/com/android/server/location/injector/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SettingsHelper.java
@@ -146,6 +146,20 @@
public abstract void removeOnGnssMeasurementsFullTrackingEnabledChangedListener(
GlobalSettingChangedListener listener);
+ /** Retrieve adas allowlist. */
+ public abstract PackageTagsList getAdasAllowlist();
+
+ /**
+ * Add a listener for changes to the ADAS settings package allowlist. Callbacks occur on an
+ * unspecified thread.
+ */
+ public abstract void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
+ /**
+ * Remove a listener for changes to the ADAS package allowlist.
+ */
+ public abstract void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
/**
* Retrieve the ignore location settings package+tags allowlist setting.
*/
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3e8da7d..777683e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -16,6 +16,7 @@
package com.android.server.location.injector;
+import static android.location.LocationDeviceConfig.ADAS_SETTINGS_ALLOWLIST;
import static android.location.LocationDeviceConfig.IGNORE_SETTINGS_ALLOWLIST;
import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
@@ -80,6 +81,7 @@
private final StringListCachedSecureSetting mLocationPackageBlacklist;
private final StringListCachedSecureSetting mLocationPackageWhitelist;
private final StringSetCachedGlobalSetting mBackgroundThrottlePackageWhitelist;
+ private final PackageTagsListSetting mAdasPackageAllowlist;
private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;
public SystemSettingsHelper(Context context) {
@@ -98,6 +100,9 @@
LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
() -> SystemConfig.getInstance().getAllowUnthrottledLocation(),
FgThread.getHandler());
+ mAdasPackageAllowlist = new PackageTagsListSetting(
+ ADAS_SETTINGS_ALLOWLIST,
+ () -> SystemConfig.getInstance().getAllowAdasLocationSettings());
mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
IGNORE_SETTINGS_ALLOWLIST,
() -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());
@@ -233,6 +238,21 @@
}
@Override
+ public PackageTagsList getAdasAllowlist() {
+ return mAdasPackageAllowlist.getValue();
+ }
+
+ @Override
+ public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.addListener(listener);
+ }
+
+ @Override
+ public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.removeListener(listener);
+ }
+
+ @Override
public PackageTagsList getIgnoreSettingsAllowlist() {
return mIgnoreSettingsPackageAllowlist.getValue();
}
@@ -359,11 +379,19 @@
PackageTagsList ignoreSettingsAllowlist = mIgnoreSettingsPackageAllowlist.getValue();
if (!ignoreSettingsAllowlist.isEmpty()) {
- ipw.println("Bypass Allow Packages:");
+ ipw.println("Emergency Bypass Allow Packages:");
ipw.increaseIndent();
ignoreSettingsAllowlist.dump(ipw);
ipw.decreaseIndent();
}
+
+ PackageTagsList adasPackageAllowlist = mAdasPackageAllowlist.getValue();
+ if (!adasPackageAllowlist.isEmpty()) {
+ ipw.println("ADAS Bypass Allow Packages:");
+ ipw.increaseIndent();
+ adasPackageAllowlist.dump(ipw);
+ ipw.decreaseIndent();
+ }
}
private abstract static class ObservingSetting extends ContentObserver {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 721ef1e..1235352 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -699,6 +699,9 @@
} else if (!mLocationSettings.getUserSettings(
getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
+ } else if (!mSettingsHelper.getAdasAllowlist().contains(
+ getIdentity().getPackageName(), getIdentity().getAttributionTag())) {
+ adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
@@ -1406,6 +1409,8 @@
this::onAppForegroundChanged;
private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
this::onBackgroundThrottleIntervalChanged;
+ private final GlobalSettingChangedListener mAdasPackageAllowlistChangedListener =
+ this::onAdasAllowlistChanged;
private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
this::onIgnoreSettingsWhitelistChanged;
private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
@@ -1710,6 +1715,9 @@
} else if (!mLocationSettings.getUserSettings(
identity.getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
+ } else if (!mSettingsHelper.getAdasAllowlist().contains(
+ identity.getPackageName(), identity.getAttributionTag())) {
+ adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
@@ -1979,6 +1987,8 @@
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.addAdasAllowlistChangedListener(
+ mAdasPackageAllowlistChangedListener);
mSettingsHelper.addIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
@@ -2000,6 +2010,7 @@
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.removeAdasAllowlistChangedListener(mAdasPackageAllowlistChangedListener);
mSettingsHelper.removeIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
@@ -2422,6 +2433,12 @@
}
}
+ private void onAdasAllowlistChanged() {
+ synchronized (mLock) {
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
+ }
+ }
+
private void onIgnoreSettingsWhitelistChanged() {
synchronized (mLock) {
updateRegistrations(Registration::onProviderLocationRequestChanged);
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 0aa384c..015bf3c5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -183,7 +183,8 @@
ActivityManagerInternal ami = LocalServices.getService(
ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
// The instrumented apks only run for testing, so we don't check user permission.
if (isCallerInstrumented) {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index bf1196d..ed71f1e 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -20,7 +20,6 @@
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -86,11 +85,14 @@
} else {
resolvedUserIds = userIds;
}
- doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- resolvedUserIds, false, broadcastAllowList, bOptions);
- if (instantUserIds != null && instantUserIds != EMPTY_INT_ARRAY) {
+
+ if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true, null, bOptions);
+ resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+ } else {
+ // send restricted broadcasts for instant apps
+ doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
+ instantUserIds, true /* isInstantApp */, null, bOptions);
}
} catch (RemoteException ex) {
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index bb2ba5c..249099d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -358,9 +358,7 @@
}
final long callingId = Binder.clearCallingIdentity();
try {
- synchronized (mPm.mInstallLock) {
- return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
- }
+ return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -429,20 +427,18 @@
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- synchronized (mPm.mInstallLock) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- // Whoever is calling forceDexOpt wants a compiled package.
- // Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
- new DexoptOptions(packageName,
- getDefaultCompilerFilter(),
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+ // Whoever is calling forceDexOpt wants a compiled package.
+ // Don't use profiles since that may cause compilation to be skipped.
+ final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
+ new DexoptOptions(packageName,
+ getDefaultCompilerFilter(),
+ DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
- throw new IllegalStateException("Failed to dexopt: " + res);
- }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
+ throw new IllegalStateException("Failed to dexopt: " + res);
}
}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 15f26e7..154f32a 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -30,7 +30,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_CHECK_MAX_SDK_VERSION;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -359,7 +359,7 @@
try {
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
// when scanning apk in apexes, we want to check the maxSdkVersion
- parseFlags |= PARSE_CHECK_MAX_SDK_VERSION;
+ parseFlags |= PARSE_APK_IN_APEX;
}
mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
scanFlags, packageParser, executorService);
diff --git a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
index 603badb..f5eee5a 100644
--- a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
+++ b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
@@ -23,11 +23,12 @@
import android.annotation.RequiresPermission;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.provider.DeviceConfig;
+import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityInterceptorCallback;
@@ -35,25 +36,28 @@
import com.android.server.wm.ActivityTaskManagerInternal;
/**
- * Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
- * that's being used to launch a user-space {@code ChooserActivity} by setting the destination
- * component to the delegated component when appropriate.
+ * Redirects Activity starts for the system bundled {@link ChooserActivity} to an external
+ * Sharesheet implementation by modifying the target component when appropriate.
+ * <p>
+ * Note: config_chooserActivity (Used also by ActivityTaskSupervisor) is already updated to point
+ * to the new instance. This value is read and used for the new target component.
*/
public final class IntentResolverInterceptor {
private static final String TAG = "IntentResolverIntercept";
-
private final Context mContext;
- private boolean mUseDelegateChooser;
+ private final ComponentName mFrameworkChooserComponent;
+ private final ComponentName mUnbundledChooserComponent;
+ private boolean mUseUnbundledSharesheet;
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@Override
public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
- if (mUseDelegateChooser && isChooserActivity(info)) {
- return new ActivityInterceptResult(
- modifyChooserIntent(info.intent),
- info.checkedOptions);
+ if (mUseUnbundledSharesheet && isSystemChooserActivity(info)) {
+ Slog.d(TAG, "Redirecting to UNBUNDLED Sharesheet");
+ info.intent.setComponent(mUnbundledChooserComponent);
+ return new ActivityInterceptResult(info.intent, info.checkedOptions);
}
return null;
}
@@ -61,10 +65,13 @@
public IntentResolverInterceptor(Context context) {
mContext = context;
+ mFrameworkChooserComponent = new ComponentName(mContext, ChooserActivity.class);
+ mUnbundledChooserComponent = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooserActivity));
}
/**
- * Start listening for intents and USE_DELEGATE_CHOOSER property changes.
+ * Start listening for intents and USE_UNBUNDLED_SHARESHEET property changes.
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public void registerListeners() {
@@ -73,36 +80,25 @@
mActivityInterceptorCallback);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(), properties -> updateUseDelegateChooser());
- updateUseDelegateChooser();
+ mContext.getMainExecutor(), properties -> updateUseUnbundledSharesheet());
+ updateUseUnbundledSharesheet();
}
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- private void updateUseDelegateChooser() {
- mUseDelegateChooser = DeviceConfig.getBoolean(
+ private void updateUseUnbundledSharesheet() {
+ mUseUnbundledSharesheet = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.USE_UNBUNDLED_SHARESHEET,
false);
+ if (mUseUnbundledSharesheet) {
+ Slog.d(TAG, "using UNBUNDLED Sharesheet");
+ } else {
+ Slog.d(TAG, "using FRAMEWORK Sharesheet");
+ }
}
- private Intent modifyChooserIntent(Intent intent) {
- intent.setComponent(getUnbundledChooserComponentName());
- return intent;
- }
-
- private static boolean isChooserActivity(ActivityInterceptorInfo info) {
- ComponentName targetComponent = new ComponentName(info.aInfo.packageName, info.aInfo.name);
-
- return targetComponent.equals(getSystemChooserComponentName())
- || targetComponent.equals(getUnbundledChooserComponentName());
- }
-
- private static ComponentName getSystemChooserComponentName() {
- return new ComponentName("android", "com.android.internal.app.ChooserActivity");
- }
-
- private static ComponentName getUnbundledChooserComponentName() {
- return ComponentName.unflattenFromString(
- Resources.getSystem().getString(R.string.config_chooserActivity));
+ private boolean isSystemChooserActivity(ActivityInterceptorInfo info) {
+ return mFrameworkChooserComponent.getPackageName().equals(info.aInfo.packageName)
+ && mFrameworkChooserComponent.getClassName().equals(info.aInfo.name);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bf80a46..f6d8d72 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3536,6 +3536,8 @@
EventLog.writeEvent(0x534e4554, "145981139", packageInfo.applicationInfo.uid,
"");
}
+ Log.w(TAG, "Missing required system package: " + packageName + (packageInfo != null
+ ? ", but found with extended search." : "."));
return null;
}
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e142ed6..d6ab78b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3694,7 +3694,7 @@
}
List<PermissionInfo> ps = mPermissionManager
.queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
- final int count = ps.size();
+ final int count = (ps == null ? 0 : ps.size());
boolean first = true;
for (int p = 0 ; p < count ; p++) {
PermissionInfo pi = ps.get(p);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d340561..ee0fdc0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5413,190 +5413,170 @@
(new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
- private static final String PREFIX_HELP_COMMAND = " ";
- private static final String PREFIX_HELP_DESCRIPTION = " ";
- private static final String PREFIX_HELP_DESCRIPTION_EXTRA_LINES = " ";
-
- private static final String CMD_HELP = "help";
- private static final String CMD_LIST = "list";
- private static final String CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS =
- "report-system-user-package-whitelist-problems";
-
- private static final String ARG_V = "-v";
- private static final String ARG_VERBOSE = "--verbose";
- private static final String ARG_ALL = "--all";
- private static final String ARG_CRITICAL_ONLY = "--critical-only";
- private static final String ARG_MODE = "--mode";
-
private final class Shell extends ShellCommand {
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutPrintWriter();
- pw.printf("User manager (user) commands:\n");
-
- pw.printf("%s%s\n", PREFIX_HELP_COMMAND, CMD_HELP);
- pw.printf("%sPrints this help text.\n\n", PREFIX_HELP_DESCRIPTION);
-
- pw.printf("%s%s [%s] [%s]\n", PREFIX_HELP_COMMAND, CMD_LIST, ARG_V, ARG_ALL);
- pw.printf("%sPrints all users on the system.\n\n", PREFIX_HELP_DESCRIPTION);
-
- pw.printf("%s%s [%s | %s] [%s] [%s MODE]\n", PREFIX_HELP_COMMAND,
- CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS,
- ARG_V, ARG_VERBOSE, ARG_CRITICAL_ONLY, ARG_MODE);
-
- pw.printf("%sReports all issues on user-type package allowlist XML files. Options:\n",
- PREFIX_HELP_DESCRIPTION);
- pw.printf("%s%s | %s: shows extra info, like number of issues\n",
- PREFIX_HELP_DESCRIPTION, ARG_V, ARG_VERBOSE);
- pw.printf("%s%s: show only critical issues, excluding warnings\n",
- PREFIX_HELP_DESCRIPTION, ARG_CRITICAL_ONLY);
- pw.printf("%s%s MODE: shows what errors would be if device used mode MODE\n"
- + "%s(where MODE is the allowlist mode integer as defined by "
- + "config_userTypePackageWhitelistMode)\n\n",
- PREFIX_HELP_DESCRIPTION, ARG_MODE, PREFIX_HELP_DESCRIPTION_EXTRA_LINES);
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("User manager (user) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println();
+ pw.println(" list [-v | --verbose] [--all]");
+ pw.println(" Prints all users on the system.");
+ pw.println();
+ pw.println(" report-system-user-package-whitelist-problems [-v | --verbose] "
+ + "[--critical-only] [--mode MODE]");
+ pw.println(" Reports all issues on user-type package allowlist XML files. Options:");
+ pw.println(" -v | --verbose: shows extra info, like number of issues");
+ pw.println(" --critical-only: show only critical issues, excluding warnings");
+ pw.println(" --mode MODE: shows what errors would be if device used mode MODE");
+ pw.println(" (where MODE is the allowlist mode integer as defined by "
+ + "config_userTypePackageWhitelistMode)");
}
- try {
- switch(cmd) {
- case CMD_LIST:
- return runList();
- case CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS:
- return runReportPackageAllowlistProblems();
- default:
- return handleDefaultCommands(cmd);
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
}
- } catch (RemoteException e) {
- getOutPrintWriter().println("Remote exception: " + e);
- }
- return -1;
- }
- private int runList() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- boolean all = false;
- boolean verbose = false;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case ARG_V:
- verbose = true;
- break;
- case ARG_ALL:
- all = true;
- break;
- default:
- pw.println("Invalid option: " + opt);
- return -1;
+ try {
+ switch(cmd) {
+ case "list":
+ return runList();
+ case "report-system-user-package-whitelist-problems":
+ return runReportPackageAllowlistProblems();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote exception: " + e);
}
+ return -1;
}
- final IActivityManager am = ActivityManager.getService();
- final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
- /* excludingDying=*/ false, /* excludePreCreated= */ !all);
- if (users == null) {
- pw.println("Error: couldn't get users");
- return 1;
- } else {
- final int size = users.size();
- int currentUser = UserHandle.USER_NULL;
- if (verbose) {
- pw.printf("%d users:\n\n", size);
- currentUser = am.getCurrentUser().id;
+
+ private int runList() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean all = false;
+ boolean verbose = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ case "--all":
+ all = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+ final IActivityManager am = ActivityManager.getService();
+ final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
+ /* excludeDying= */ false, /* excludePreCreated= */ !all);
+ if (users == null) {
+ pw.println("Error: couldn't get users");
+ return 1;
} else {
- // NOTE: the standard "list users" command is used by integration tests and
- // hence should not be changed. If you need to add more info, use the
- // verbose option.
- pw.println("Users:");
- }
- for (int i = 0; i < size; i++) {
- final UserInfo user = users.get(i);
- final boolean running = am.isUserRunning(user.id, 0);
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ final int size = users.size();
+ int currentUser = UserHandle.USER_NULL;
if (verbose) {
- final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
- String deviceOwner = "";
- String profileOwner = "";
- if (dpm != null) {
- final long ident = Binder.clearCallingIdentity();
- // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
- try {
- if (dpm.getDeviceOwnerUserId() == user.id) {
- deviceOwner = " (device-owner)";
- }
- if (dpm.getProfileOwnerAsUser(user.id) != null) {
- profileOwner = " (profile-owner)";
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
- i,
- user.id,
- user.name,
- user.userType.replace("android.os.usertype.", ""),
- UserInfo.flagsToString(user.flags),
- hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
- running ? " (running)" : "",
- user.partial ? " (partial)" : "",
- user.preCreated ? " (pre-created)" : "",
- user.convertedFromPreCreated ? " (converted)" : "",
- deviceOwner, profileOwner,
- current ? " (current)" : "");
+ pw.printf("%d users:\n\n", size);
+ currentUser = am.getCurrentUser().id;
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
// verbose option.
- pw.printf("\t%s%s\n", user, running ? " running" : "");
+ pw.println("Users:");
}
+ for (int i = 0; i < size; i++) {
+ final UserInfo user = users.get(i);
+ final boolean running = am.isUserRunning(user.id, 0);
+ final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ if (verbose) {
+ final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
+ String deviceOwner = "";
+ String profileOwner = "";
+ if (dpm != null) {
+ final long ident = Binder.clearCallingIdentity();
+ // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
+ try {
+ if (dpm.getDeviceOwnerUserId() == user.id) {
+ deviceOwner = " (device-owner)";
+ }
+ if (dpm.getProfileOwnerAsUser(user.id) != null) {
+ profileOwner = " (profile-owner)";
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+ i,
+ user.id,
+ user.name,
+ user.userType.replace("android.os.usertype.", ""),
+ UserInfo.flagsToString(user.flags),
+ hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
+ running ? " (running)" : "",
+ user.partial ? " (partial)" : "",
+ user.preCreated ? " (pre-created)" : "",
+ user.convertedFromPreCreated ? " (converted)" : "",
+ deviceOwner, profileOwner,
+ current ? " (current)" : "");
+ } else {
+ // NOTE: the standard "list users" command is used by integration tests and
+ // hence should not be changed. If you need to add more info, use the
+ // verbose option.
+ pw.printf("\t%s%s\n", user, running ? " running" : "");
+ }
+ }
+ return 0;
+ }
+ }
+
+ private int runReportPackageAllowlistProblems() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ boolean criticalOnly = false;
+ int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ case "--critical-only":
+ criticalOnly = true;
+ break;
+ case "--mode":
+ mode = Integer.parseInt(getNextArgRequired());
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+
+ Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
+ + ", criticalOnly=" + criticalOnly
+ + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
+
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
+ criticalOnly);
}
return 0;
}
- }
- private int runReportPackageAllowlistProblems() {
- final PrintWriter pw = getOutPrintWriter();
- boolean verbose = false;
- boolean criticalOnly = false;
- int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case ARG_V:
- case ARG_VERBOSE:
- verbose = true;
- break;
- case ARG_CRITICAL_ONLY:
- criticalOnly = true;
- break;
- case ARG_MODE:
- mode = Integer.parseInt(getNextArgRequired());
- break;
- default:
- pw.println("Invalid option: " + opt);
- return -1;
- }
- }
-
- Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
- + ", criticalOnly=" + criticalOnly
- + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
-
- try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
- mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
- criticalOnly);
- }
- return 0;
- }
- }
+ } // class Shell
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 7e4da94..4fae6b8 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -45,7 +45,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
@@ -94,8 +93,6 @@
private final Context mContext;
private IPackageManager mPackageManager;
- private final Object mInstallLock;
- @GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Handler mHandler;
@@ -105,10 +102,9 @@
}
public ArtManagerService(Context context, Installer installer,
- Object installLock) {
+ Object ignored) {
mContext = context;
mInstaller = installer;
- mInstallLock = installLock;
mHandler = new Handler(BackgroundThread.getHandler().getLooper());
LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
@@ -273,16 +269,14 @@
private void createProfileSnapshot(String packageName, String profileName, String classpath,
int appId, ISnapshotRuntimeProfileCallback callback) {
// Ask the installer to snapshot the profile.
- synchronized (mInstallLock) {
- try {
- if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- return;
- }
- } catch (InstallerException e) {
+ try {
+ if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
+ } catch (InstallerException e) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
}
// Open the snapshot and invoke the callback.
@@ -308,13 +302,11 @@
Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
}
- synchronized (mInstallLock) {
- try {
- mInstaller.destroyProfileSnapshot(packageName, profileName);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to destroy profile snapshot for " +
- packageName + ":" + profileName, e);
- }
+ try {
+ mInstaller.destroyProfileSnapshot(packageName, profileName);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
+ e);
}
}
@@ -480,9 +472,7 @@
for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
String codePath = packageProfileNames.keyAt(i);
String profileName = packageProfileNames.valueAt(i);
- synchronized (mInstallLock) {
- mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
- }
+ mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
}
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dump profiles", e);
@@ -512,10 +502,8 @@
") to " + outDexFile);
final long callingId = Binder.clearCallingIdentity();
try {
- synchronized (mInstallLock) {
- return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
- pkg.getUid());
- }
+ return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
+ pkg.getUid());
} finally {
Binder.restoreCallingIdentity(callingId);
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f255db4..9897c42 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -209,8 +209,6 @@
public static final int SDK_VERSION = Build.VERSION.SDK_INT;
public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
- public static final String[] PREVIOUS_CODENAMES =
- Build.VERSION.KNOWN_CODENAMES.toArray(new String[]{});
public static boolean sCompatibilityModeEnabled = true;
public static boolean sUseRoundIcon = false;
@@ -238,7 +236,7 @@
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
- public static final int PARSE_CHECK_MAX_SDK_VERSION = 1 << 9;
+ public static final int PARSE_APK_IN_APEX = 1 << 9;
public static final int PARSE_CHATTY = 1 << 31;
@@ -1534,7 +1532,7 @@
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
if (SDK_VERSION > 0) {
- final boolean checkMaxSdkVersion = (flags & PARSE_CHECK_MAX_SDK_VERSION) != 0;
+ final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
try {
int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
@@ -1569,7 +1567,7 @@
targetCode = minCode;
}
- if (checkMaxSdkVersion) {
+ if (isApkInApex) {
val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
if (val != null) {
// maxSdkVersion only supports integer
@@ -1578,7 +1576,8 @@
}
ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils
- .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input);
+ .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input,
+ isApkInApex);
if (targetSdkVersionResult.isError()) {
return input.error(targetSdkVersionResult);
}
@@ -1601,7 +1600,7 @@
pkg.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
- if (checkMaxSdkVersion) {
+ if (isApkInApex) {
ParseResult<Integer> maxSdkVersionResult = FrameworkParsingPackageUtils
.computeMaxSdkVersion(maxVers, SDK_VERSION, input);
if (maxSdkVersionResult.isError()) {
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 70d69c6..e157a27 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -187,8 +187,12 @@
initializeActivityRecognizersTags();
- // If this device does not have telephony, restrict the phone call ops
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ // Restrict phone call ops if the TelecomService will not start (conditioned on having
+ // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY).
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken,
null, UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 2f68f56..bce1cce 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -29,6 +29,7 @@
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -77,6 +78,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.graphics.drawable.Icon;
import android.hardware.ISensorPrivacyListener;
@@ -155,6 +157,7 @@
private final AppOpsManager mAppOpsManager;
private final AppOpsManagerInternal mAppOpsManagerInternal;
private final TelephonyManager mTelephonyManager;
+ private final PackageManagerInternal mPackageManagerInternal;
private CameraPrivacyLightController mCameraPrivacyLightController;
@@ -178,6 +181,7 @@
mActivityManagerInternal = getLocalService(ActivityManagerInternal.class);
mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
}
@@ -828,6 +832,12 @@
* sensor privacy.
*/
private void enforceObserveSensorPrivacyPermission() {
+ String systemUIPackage = mContext.getString(R.string.config_systemUi);
+ if (Binder.getCallingUid() == mPackageManagerInternal
+ .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+ // b/221782106, possible race condition with role grant might bootloop device.
+ return;
+ }
enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
"Observing sensor privacy changes requires the following permission: "
+ android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index c0ab65a..05d92be 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -17,7 +17,6 @@
package com.android.server.soundtrigger_middleware;
import android.annotation.NonNull;
-import android.media.permission.SafeCloseable;
import android.media.soundtrigger.ModelParameterRange;
import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
@@ -30,6 +29,7 @@
import android.os.IBinder;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
@@ -63,18 +63,24 @@
*/
public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal,
ICaptureStateNotifier.Listener {
- private final @NonNull ISoundTriggerHal mDelegate;
+ @NonNull private final ISoundTriggerHal mDelegate;
private GlobalCallback mGlobalCallback;
+ /**
+ * This lock must be held to synchronize forward calls (start/stop/onCaptureStateChange) that
+ * update the mActiveModels set and mCaptureState.
+ * It must not be locked in HAL callbacks to avoid deadlocks.
+ */
+ @NonNull private final Object mStartStopLock = new Object();
/**
* Information about a model that is currently loaded. This is needed in order to be able to
* send abort events to its designated callback.
*/
private static class LoadedModel {
- final int type;
- final @NonNull ModelCallback callback;
+ public final int type;
+ @NonNull public final ModelCallback callback;
- private LoadedModel(int type, @NonNull ModelCallback callback) {
+ LoadedModel(int type, @NonNull ModelCallback callback) {
this.type = type;
this.callback = callback;
}
@@ -83,19 +89,19 @@
/**
* This map holds the model type for every model that is loaded.
*/
- private final @NonNull Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
+ @NonNull private final Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
/**
* A set of all models that are currently active.
* We use this in order to know which models to stop in case of external capture.
* Used as a lock to synchronize operations that effect activity.
*/
- private final @NonNull Set<Integer> mActiveModels = new HashSet<>();
+ @NonNull private final Set<Integer> mActiveModels = new HashSet<>();
/**
* Notifier for changes in capture state.
*/
- private final @NonNull ICaptureStateNotifier mNotifier;
+ @NonNull private final ICaptureStateNotifier mNotifier;
/**
* Whether capture is active.
@@ -106,10 +112,10 @@
* Since we're wrapping the death recipient, we need to keep a translation map for unlinking.
* Key is the client recipient, value is the wrapper.
*/
- private final @NonNull Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
+ @NonNull private final Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
mDeathRecipientMap = new ConcurrentHashMap<>();
- private final @NonNull CallbackThread mCallbackThread = new CallbackThread();
+ @NonNull private final CallbackThread mCallbackThread = new CallbackThread();
public SoundTriggerHalConcurrentCaptureHandler(
@NonNull ISoundTriggerHal delegate,
@@ -122,20 +128,28 @@
@Override
public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
RecognitionConfig config) {
- synchronized (mActiveModels) {
- if (mCaptureState) {
- throw new RecoverableException(Status.RESOURCE_CONTENTION);
+ synchronized (mStartStopLock) {
+ synchronized (mActiveModels) {
+ if (mCaptureState) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION);
+ }
+ mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+ mActiveModels.add(modelHandle);
}
- mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
- mActiveModels.add(modelHandle);
}
}
@Override
public void stopRecognition(int modelHandle) {
- synchronized (mActiveModels) {
- mDelegate.stopRecognition(modelHandle);
- mActiveModels.remove(modelHandle);
+ synchronized (mStartStopLock) {
+ boolean wasActive;
+ synchronized (mActiveModels) {
+ wasActive = mActiveModels.remove(modelHandle);
+ }
+ if (wasActive) {
+ // Must be done outside of the lock, since it may trigger synchronous callbacks.
+ mDelegate.stopRecognition(modelHandle);
+ }
}
// Block until all previous events are delivered. Since this is potentially blocking on
// upward calls, it must be done outside the lock.
@@ -144,27 +158,38 @@
@Override
public void onCaptureStateChange(boolean active) {
- synchronized (mActiveModels) {
+ synchronized (mStartStopLock) {
if (active) {
- // Abort all active models. This must be done as one transaction to the event
- // thread, in order to be able to dedupe events before they are delivered.
- try (SafeCloseable ignored = mCallbackThread.stallReader()) {
- for (int modelHandle : mActiveModels) {
- mDelegate.stopRecognition(modelHandle);
- LoadedModel model = mLoadedModels.get(modelHandle);
- // An abort event must be the last one for its model.
- mCallbackThread.pushWithDedupe(modelHandle, true,
- () -> notifyAbort(modelHandle, model));
- }
- }
+ abortAllActiveModels();
} else {
- mGlobalCallback.onResourcesAvailable();
+ if (mGlobalCallback != null) {
+ mGlobalCallback.onResourcesAvailable();
+ }
}
-
mCaptureState = active;
}
}
+ private void abortAllActiveModels() {
+ while (true) {
+ int toStop;
+ synchronized (mActiveModels) {
+ Iterator<Integer> iterator = mActiveModels.iterator();
+ if (!iterator.hasNext()) {
+ return;
+ }
+ toStop = iterator.next();
+ mActiveModels.remove(toStop);
+ }
+ // Invoke stop outside of the lock.
+ mDelegate.stopRecognition(toStop);
+
+ LoadedModel model = mLoadedModels.get(toStop);
+ // Queue an abort event (no need to flush).
+ mCallbackThread.push(() -> notifyAbort(toStop, model));
+ }
+ }
+
@Override
public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
int handle = mDelegate.loadSoundModel(soundModel, new CallbackWrapper(callback));
@@ -188,23 +213,13 @@
@Override
public void registerCallback(GlobalCallback callback) {
- mGlobalCallback = new GlobalCallback() {
- @Override
- public void onResourcesAvailable() {
- mCallbackThread.push(callback::onResourcesAvailable);
- }
- };
+ mGlobalCallback = () -> mCallbackThread.push(callback::onResourcesAvailable);
mDelegate.registerCallback(mGlobalCallback);
}
@Override
public void linkToDeath(IBinder.DeathRecipient recipient) {
- IBinder.DeathRecipient wrapper = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mCallbackThread.push(() -> recipient.binderDied());
- }
- };
+ IBinder.DeathRecipient wrapper = () -> mCallbackThread.push(recipient::binderDied);
mDelegate.linkToDeath(wrapper);
mDeathRecipientMap.put(recipient, wrapper);
}
@@ -215,7 +230,7 @@
}
private class CallbackWrapper implements ISoundTriggerHal.ModelCallback {
- private final @NonNull ISoundTriggerHal.ModelCallback mDelegateCallback;
+ @NonNull private final ISoundTriggerHal.ModelCallback mDelegateCallback;
private CallbackWrapper(@NonNull ModelCallback delegateCallback) {
mDelegateCallback = delegateCallback;
@@ -223,18 +238,36 @@
@Override
public void recognitionCallback(int modelHandle, RecognitionEvent event) {
- // A recognition event must be the last one for its model, unless it is a forced one
- // (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle, !event.recognitionStillActive,
- () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+ synchronized (mActiveModels) {
+ if (!mActiveModels.contains(modelHandle)) {
+ // Discard the event.
+ return;
+ }
+ if (!event.recognitionStillActive) {
+ mActiveModels.remove(modelHandle);
+ }
+ // A recognition event must be the last one for its model, unless it indicates that
+ // recognition is still active.
+ mCallbackThread.push(
+ () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+ }
}
@Override
public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
- // A recognition event must be the last one for its model, unless it is a forced one
- // (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle, !event.common.recognitionStillActive,
- () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+ synchronized (mActiveModels) {
+ if (!mActiveModels.contains(modelHandle)) {
+ // Discard the event.
+ return;
+ }
+ if (!event.common.recognitionStillActive) {
+ mActiveModels.remove(modelHandle);
+ }
+ // A recognition event must be the last one for its model, unless it indicates that
+ // recognition is still active.
+ mCallbackThread.push(
+ () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+ }
}
@Override
@@ -254,36 +287,12 @@
* <ul>
* <li>Events are processed on a separate thread than the thread that pushed them, in the order
* they were pushed.
- * <li>Events can be deduped upon entry to the queue. This is achieved as follows:
- * <ul>
- * <li>Temporarily stall the reader via {@link #stallReader()}.
- * <li>Within this scope, push as many events as needed via
- * {@link #pushWithDedupe(int, boolean, Runnable)}.
- * If an event with the same model handle as the one being pushed is already in the queue
- * and has been marked as "lastForModel", the new event will be discarded before entering
- * the queue.
- * <li>Finally, un-stall the reader by existing the scope.
- * <li>Events that do not require deduping can be pushed via {@link #push(Runnable)}.
- * </ul>
* <li>Events can be flushed via {@link #flush()}. This will block until all events pushed prior
* to this call have been fully processed.
* </ul>
*/
private static class CallbackThread {
- private static class Entry {
- final boolean lastForModel;
- final int modelHandle;
- final Runnable runnable;
-
- private Entry(boolean lastForModel, int modelHandle, Runnable runnable) {
- this.lastForModel = lastForModel;
- this.modelHandle = modelHandle;
- this.runnable = runnable;
- }
- }
-
- private boolean mStallReader = false;
- private final Queue<Entry> mList = new LinkedList<>();
+ private final Queue<Runnable> mList = new LinkedList<>();
private int mPushCount = 0;
private int mProcessedCount = 0;
@@ -312,23 +321,11 @@
* @param runnable The runnable to push.
*/
void push(Runnable runnable) {
- pushEntry(new Entry(false, 0, runnable), false);
- }
-
-
- /**
- * Push a new runnable to the queue, with deduping.
- * If an entry with the same model handle is already in the queue and was designated as
- * last for model, this one will be discarded.
- *
- * @param modelHandle The model handle, used for deduping purposes.
- * @param lastForModel If true, this entry will be considered the last one for this model
- * and any subsequence calls for this handle (whether lastForModel or
- * not) will be discarded while this entry is in the queue.
- * @param runnable The runnable to push.
- */
- void pushWithDedupe(int modelHandle, boolean lastForModel, Runnable runnable) {
- pushEntry(new Entry(lastForModel, modelHandle, runnable), true);
+ synchronized (mList) {
+ mList.add(runnable);
+ mPushCount++;
+ mList.notifyAll();
+ }
}
/**
@@ -346,45 +343,15 @@
}
}
- /**
- * Creates a scope (using a try-with-resources block), within which events that are pushed
- * remain queued and processed. This is useful in order to utilize deduping.
- */
- SafeCloseable stallReader() {
- synchronized (mList) {
- mStallReader = true;
- return () -> {
- synchronized (mList) {
- mStallReader = false;
- mList.notifyAll();
- }
- };
- }
- }
-
- private void pushEntry(Entry entry, boolean dedupe) {
- synchronized (mList) {
- if (dedupe) {
- for (Entry existing : mList) {
- if (existing.lastForModel && existing.modelHandle == entry.modelHandle) {
- return;
- }
- }
- }
- mList.add(entry);
- mPushCount++;
- mList.notifyAll();
- }
- }
-
private Runnable pop() throws InterruptedException {
synchronized (mList) {
- while (mStallReader || mList.isEmpty()) {
+ while (mList.isEmpty()) {
mList.wait();
}
- return mList.remove().runnable;
+ return mList.remove();
}
}
+
}
/** Notify the client that recognition has been aborted. */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index eb5ca9c..95de040 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -897,6 +897,7 @@
proc.getThread(), r.token);
final boolean isTransitionForward = r.isTransitionForward();
+ final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
@@ -907,7 +908,7 @@
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
- r.shareableActivityToken, r.getLaunchedFromBubble()));
+ r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8d1425d..67dd89e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -29,6 +29,7 @@
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
+import android.view.Display;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.SurfaceControl;
@@ -194,6 +195,9 @@
int firstExternalDisplayId = DEFAULT_DISPLAY;
for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+ if (displayContent.getDisplayInfo().state == Display.STATE_OFF) {
+ continue;
+ }
// Heuristic solution here. Currently when "Freeform windows" developer option is
// enabled we automatically put secondary displays in freeform mode and emulating
// "desktop mode". It also makes sense to show the pointer on the same display.
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 00f7e636..65062e5 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -249,6 +249,8 @@
inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
inputWindowHandle.setWindowToken(w.mClient);
+ inputWindowHandle.setName(w.getName());
+
// Update layout params flags to force the window to be not touch modal. We do this to
// restrict the window's touchable region to the task even if it requests touches outside
// its window bounds. An example is a dialog in primary split should get touches outside its
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3a458fd..900963e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2262,7 +2262,7 @@
mFragmentToken,
mRemoteToken.toWindowContainerToken(),
getConfiguration(),
- getChildCount() == 0,
+ runningActivityCount[0] == 0,
runningActivityCount[0],
isVisible(),
childActivities,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 31b5579..fa5e450 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,7 +106,6 @@
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
- jmethodID checkInjectEventsPermission;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -131,6 +130,11 @@
static struct {
jclass clazz;
+ jfieldID mPtr;
+} gNativeInputManagerServiceImpl;
+
+static struct {
+ jclass clazz;
} gInputDeviceClassInfo;
static struct {
@@ -260,17 +264,16 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env,
- const std::string& name);
- base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId,
+ base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
+ base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
const std::string& name,
int32_t pid);
- status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
+ status_t removeInputChannel(const sp<IBinder>& connectionToken);
status_t pilferPointers(const sp<IBinder>& token);
void displayRemoved(JNIEnv* env, int32_t displayId);
void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
- void setFocusedDisplay(JNIEnv* env, int32_t displayId);
+ void setFocusedDisplay(int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerSpeed(int32_t speed);
@@ -329,7 +332,6 @@
bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
- bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -512,19 +514,18 @@
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
- JNIEnv* /* env */, const std::string& name) {
+ const std::string& name) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputChannel(name);
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
- JNIEnv* /* env */, int32_t displayId, const std::string& name, int32_t pid) {
+ int32_t displayId, const std::string& name, int32_t pid) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
}
-status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */,
- const sp<IBinder>& connectionToken) {
+status_t NativeInputManager::removeInputChannel(const sp<IBinder>& connectionToken) {
ATRACE_CALL();
return mInputManager->getDispatcher().removeInputChannel(connectionToken);
}
@@ -1002,7 +1003,7 @@
mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
}
-void NativeInputManager::setFocusedDisplay(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(int32_t displayId) {
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
@@ -1368,18 +1369,6 @@
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
}
-bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
- int32_t injectorPid, int32_t injectorUid) {
- ATRACE_CALL();
- JNIEnv* env = jniEnv();
- jboolean result = env->CallBooleanMethod(mServiceObj,
- gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
- if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
- result = false;
- }
- return result;
-}
-
void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1504,6 +1493,11 @@
// ----------------------------------------------------------------------------
+static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
+ return reinterpret_cast<NativeInputManager*>(
+ env->GetLongField(clazz, gNativeInputManagerServiceImpl.mPtr));
+}
+
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -1518,8 +1512,8 @@
return reinterpret_cast<jlong>(im);
}
-static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeStart(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
status_t result = im->getInputManager()->start();
if (result) {
@@ -1527,39 +1521,39 @@
}
}
-static void nativeSetDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray viewportObjArray) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetDisplayViewports(JNIEnv* env, jobject nativeImplObj,
+ jobjectArray viewportObjArray) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setDisplayViewports(env, viewportObjArray);
}
-static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint scanCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint sourceMask, jint scanCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getScanCodeState(deviceId, uint32_t(sourceMask),
scanCode);
}
-static jint nativeGetKeyCodeState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint keyCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint sourceMask, jint keyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getKeyCodeState(deviceId, uint32_t(sourceMask),
keyCode);
}
-static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint sw) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetSwitchState(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+ jint sw) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getSwitchState(deviceId, uint32_t(sourceMask),
sw);
}
-static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+ jintArray keyCodes, jbooleanArray outFlags) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
int32_t* codes = env->GetIntArrayElements(keyCodes, nullptr);
uint8_t* flags = env->GetBooleanArrayElements(outFlags, nullptr);
@@ -1581,9 +1575,9 @@
return result;
}
-static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint deviceId, jint locationKeyCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint locationKeyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId,
locationKeyCode);
}
@@ -1596,17 +1590,16 @@
ALOGW("Input channel object '%s' was disposed without first being removed with "
"the input manager!",
inputChannel->getName().c_str());
- im->removeInputChannel(env, inputChannel->getConnectionToken());
+ im->removeInputChannel(inputChannel->getConnectionToken());
}
-static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jstring nameObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstring nameObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
ScopedUtfChars nameChars(env, nameObj);
std::string name = nameChars.c_str();
- base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(env, name);
+ base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(name);
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1626,9 +1619,9 @@
return inputChannelObj;
}
-static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId,
+static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint displayId,
jstring nameObj, jint pid) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (displayId == ADISPLAY_ID_NONE) {
std::string message = "InputChannel used as a monitor must be associated with a display";
@@ -1640,7 +1633,7 @@
std::string name = nameChars.c_str();
base::Result<std::unique_ptr<InputChannel>> inputChannel =
- im->createInputMonitor(env, displayId, name, pid);
+ im->createInputMonitor(displayId, name, pid);
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1657,11 +1650,11 @@
return inputChannelObj;
}
-static void nativeRemoveInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRemoveInputChannel(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
- status_t status = im->removeInputChannel(env, token);
+ status_t status = im->removeInputChannel(token);
if (status && status != BAD_VALUE) { // ignore already removed channel
std::string message;
message += StringPrintf("Failed to remove input channel. status=%d", status);
@@ -1669,48 +1662,46 @@
}
}
-static void nativePilferPointers(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativePilferPointers(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
im->pilferPointers(token);
}
-static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputFilterEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setInputFilterEnabled(enabled);
}
-static jboolean nativeSetInTouchMode(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean inTouchMode, jint pid, jint uid,
- jboolean hasPermission) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolean inTouchMode,
+ jint pid, jint uid, jboolean hasPermission) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, pid, uid,
hasPermission);
}
-static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jfloat opacity) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
+ jfloat opacity) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setMaximumObscuringOpacityForTouch(opacity);
}
-static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint mode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jobject nativeImplObj, jint mode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setBlockUntrustedTouchesMode(
static_cast<BlockUntrustedTouchesMode>(mode));
}
-static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
- jint syncMode, jint timeoutMillis, jint policyFlags) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj,
+ jboolean injectIntoUid, jint uid, jint syncMode,
+ jint timeoutMillis, jint policyFlags) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
// static_cast is safe because the value was already checked at the Java layer
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
@@ -1723,8 +1714,7 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1737,8 +1727,8 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
+ mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1749,9 +1739,8 @@
}
}
-static jobject nativeVerifyInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobject inputEventObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeVerifyInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
KeyEvent keyEvent;
@@ -1792,56 +1781,53 @@
}
}
-static void nativeToggleCapsLock(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().toggleCapsLockState(deviceId);
}
-static void nativeDisplayRemoved(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->displayRemoved(env, displayId);
}
-static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint displayId, jobject applicationHandleObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jobject applicationHandleObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setFocusedApplication(env, displayId, applicationHandleObj);
}
-static void nativeSetFocusedDisplay(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint displayId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedDisplay(env, displayId);
+ im->setFocusedDisplay(displayId);
}
-static void nativeRequestPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobject tokenObj, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> windowToken = ibinderForJavaObject(env, tokenObj);
im->requestPointerCapture(windowToken, enabled);
}
-static void nativeSetInputDispatchMode(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputDispatchMode(JNIEnv* env, jobject nativeImplObj, jboolean enabled,
+ jboolean frozen) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDispatchMode(enabled, frozen);
}
-static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean lightsOut) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetSystemUiLightsOut(JNIEnv* env, jobject nativeImplObj, jboolean lightsOut) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setSystemUiLightsOut(lightsOut);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
jobject fromChannelTokenObj, jobject toChannelTokenObj,
jboolean isDragDrop) {
if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
@@ -1851,7 +1837,7 @@
sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouchFocus(fromChannelToken, toChannelToken,
isDragDrop)) {
return JNI_TRUE;
@@ -1860,11 +1846,11 @@
}
}
-static jboolean nativeTransferTouch(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouch(JNIEnv* env, jobject nativeImplObj,
jobject destChannelTokenObj) {
sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouch(destChannelToken)) {
return JNI_TRUE;
} else {
@@ -1872,42 +1858,39 @@
}
}
-static void nativeSetPointerSpeed(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint speed) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerSpeed(speed);
}
-static void nativeSetPointerAcceleration(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jfloat acceleration) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerAcceleration(JNIEnv* env, jobject nativeImplObj, jfloat acceleration) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerAcceleration(acceleration);
}
-static void nativeSetShowTouches(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setShowTouches(enabled);
}
-static void nativeSetInteractive(JNIEnv* env,
- jclass clazz, jlong ptr, jboolean interactive) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInteractive(interactive);
}
-static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->reloadCalibration();
}
-static void nativeVibrate(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jlongArray patternObj, jintArray amplitudesObj, jint repeat, jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jlongArray patternObj,
+ jintArray amplitudesObj, jint repeat, jint token) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
size_t patternSize = env->GetArrayLength(patternObj);
if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
@@ -1940,10 +1923,10 @@
im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
}
-static void nativeVibrateCombined(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeVibrateCombined(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jlongArray patternObj, jobject amplitudesObj, jint repeat,
jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
size_t patternSize = env->GetArrayLength(patternObj);
@@ -1990,21 +1973,20 @@
im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
}
-static void nativeCancelVibrate(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId, jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint token) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().cancelVibrate(deviceId, token);
}
-static bool nativeIsVibrating(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static bool nativeIsVibrating(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().isVibrating(deviceId);
}
-static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jintArray nativeGetVibratorIds(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::vector<int32_t> vibrators = im->getInputManager()->getReader().getVibratorIds(deviceId);
jintArray vibIdArray = env->NewIntArray(vibrators.size());
@@ -2014,8 +1996,8 @@
return vibIdArray;
}
-static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor);
std::vector<InputDeviceLightInfo> lights =
@@ -2059,9 +2041,9 @@
return jLights;
}
-static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jint nativeGetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint lightId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret =
im->getInputManager()->getReader().getLightPlayerId(deviceId, lightId);
@@ -2069,54 +2051,51 @@
return static_cast<jint>(ret.value_or(0));
}
-static jint nativeGetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret =
im->getInputManager()->getReader().getLightColor(deviceId, lightId);
return static_cast<jint>(ret.value_or(0));
}
-static void nativeSetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId, jint playerId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+ jint playerId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().setLightPlayerId(deviceId, lightId, playerId);
}
-static void nativeSetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId, jint color) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+ jint color) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().setLightColor(deviceId, lightId, color);
}
-static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryCapacity(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryCapacity(deviceId);
return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
}
-static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryStatus(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryStatus(deviceId);
return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
}
-static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadKeyboardLayouts(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS);
}
-static void nativeReloadDeviceAliases(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
@@ -2131,58 +2110,54 @@
return out;
}
-static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+static jstring nativeDump(JNIEnv* env, jobject nativeImplObj) {
std::string dump = dumpInputProperties();
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->dump(dump);
return env->NewStringUTF(dump.c_str());
}
-static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeMonitor(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().monitor();
im->getInputManager()->getDispatcher().monitor();
}
-static jboolean nativeIsInputDeviceEnabled(JNIEnv* env /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeIsInputDeviceEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().isInputDeviceEnabled(deviceId);
}
-static void nativeEnableInputDevice(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeEnableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDeviceEnabled(deviceId, true);
}
-static void nativeDisableInputDevice(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDeviceEnabled(deviceId, false);
}
-static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerIconType(JNIEnv* env, jobject nativeImplObj, jint iconId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerIconType(iconId);
}
-static void nativeReloadPointerIcons(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadPointerIcons(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->reloadPointerIcons();
}
-static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject iconObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
PointerIcon pointerIcon;
status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
@@ -2196,40 +2171,38 @@
im->setCustomPointerIcon(spriteIcon);
}
-static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint deviceId, jint displayId) {
-
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
}
-static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->notifyPointerDisplayIdChanged();
}
-static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
isEligible);
}
-static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeSetMotionClassifierEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setMotionClassifierEnabled(enabled);
}
@@ -2265,8 +2238,8 @@
return sensorInfo;
}
-static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobjectArray nativeGetSensorList(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::vector<InputDeviceSensorInfo> sensors =
im->getInputManager()->getReader().getSensors(deviceId);
@@ -2295,10 +2268,10 @@
return arr;
}
-static jboolean nativeEnableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeEnableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType, jint samplingPeriodUs,
jint maxBatchReportLatencyUs) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()
->getReader()
@@ -2307,18 +2280,18 @@
std::chrono::microseconds(maxBatchReportLatencyUs));
}
-static void nativeDisableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeDisableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().disableSensor(deviceId,
static_cast<InputDeviceSensorType>(
sensorType));
}
-static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeFlushSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().flushSensor(deviceId,
static_cast<InputDeviceSensorType>(sensorType));
@@ -2327,8 +2300,8 @@
sensorType));
}
-static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().cancelCurrentTouch();
}
@@ -2336,87 +2309,84 @@
static const JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
- {"nativeInit",
+ {"init",
"(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/"
"MessageQueue;)J",
(void*)nativeInit},
- {"nativeStart", "(J)V", (void*)nativeStart},
- {"nativeSetDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V",
+ {"start", "()V", (void*)nativeStart},
+ {"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
- {"nativeGetScanCodeState", "(JIII)I", (void*)nativeGetScanCodeState},
- {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
- {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
- {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
- {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
- {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
+ {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
+ {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
+ {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+ {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
+ {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
+ {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
(void*)nativeCreateInputChannel},
- {"nativeCreateInputMonitor", "(JILjava/lang/String;I)Landroid/view/InputChannel;",
+ {"createInputMonitor", "(ILjava/lang/String;I)Landroid/view/InputChannel;",
(void*)nativeCreateInputMonitor},
- {"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
- {"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers},
- {"nativeSetInputFilterEnabled", "(JZ)V", (void*)nativeSetInputFilterEnabled},
- {"nativeSetInTouchMode", "(JZIIZ)Z", (void*)nativeSetInTouchMode},
- {"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
+ {"removeInputChannel", "(Landroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
+ {"pilferPointers", "(Landroid/os/IBinder;)V", (void*)nativePilferPointers},
+ {"setInputFilterEnabled", "(Z)V", (void*)nativeSetInputFilterEnabled},
+ {"setInTouchMode", "(ZIIZ)Z", (void*)nativeSetInTouchMode},
+ {"setMaximumObscuringOpacityForTouch", "(F)V",
(void*)nativeSetMaximumObscuringOpacityForTouch},
- {"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
- {"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
- (void*)nativeInjectInputEvent},
- {"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
+ {"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode},
+ {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent},
+ {"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
- {"nativeToggleCapsLock", "(JI)V", (void*)nativeToggleCapsLock},
- {"nativeDisplayRemoved", "(JI)V", (void*)nativeDisplayRemoved},
- {"nativeSetFocusedApplication", "(JILandroid/view/InputApplicationHandle;)V",
+ {"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
+ {"displayRemoved", "(I)V", (void*)nativeDisplayRemoved},
+ {"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
- {"nativeSetFocusedDisplay", "(JI)V", (void*)nativeSetFocusedDisplay},
- {"nativeRequestPointerCapture", "(JLandroid/os/IBinder;Z)V",
- (void*)nativeRequestPointerCapture},
- {"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode},
- {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut},
- {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;Z)Z",
+ {"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+ {"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
+ {"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
+ {"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
+ {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
(void*)nativeTransferTouchFocus},
- {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
- {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
- {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
- {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
- {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
- {"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
- {"nativeVibrate", "(JI[J[III)V", (void*)nativeVibrate},
- {"nativeVibrateCombined", "(JI[JLandroid/util/SparseArray;II)V",
- (void*)nativeVibrateCombined},
- {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
- {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
- {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
- {"nativeGetLights", "(JI)Ljava/util/List;", (void*)nativeGetLights},
- {"nativeGetLightPlayerId", "(JII)I", (void*)nativeGetLightPlayerId},
- {"nativeGetLightColor", "(JII)I", (void*)nativeGetLightColor},
- {"nativeSetLightPlayerId", "(JIII)V", (void*)nativeSetLightPlayerId},
- {"nativeSetLightColor", "(JIII)V", (void*)nativeSetLightColor},
- {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
- {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
- {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
- {"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
- {"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
- {"nativeMonitor", "(J)V", (void*)nativeMonitor},
- {"nativeIsInputDeviceEnabled", "(JI)Z", (void*)nativeIsInputDeviceEnabled},
- {"nativeEnableInputDevice", "(JI)V", (void*)nativeEnableInputDevice},
- {"nativeDisableInputDevice", "(JI)V", (void*)nativeDisableInputDevice},
- {"nativeSetPointerIconType", "(JI)V", (void*)nativeSetPointerIconType},
- {"nativeReloadPointerIcons", "(J)V", (void*)nativeReloadPointerIcons},
- {"nativeSetCustomPointerIcon", "(JLandroid/view/PointerIcon;)V",
+ {"transferTouch", "(Landroid/os/IBinder;)Z", (void*)nativeTransferTouch},
+ {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
+ {"setPointerAcceleration", "(F)V", (void*)nativeSetPointerAcceleration},
+ {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
+ {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
+ {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
+ {"vibrate", "(I[J[III)V", (void*)nativeVibrate},
+ {"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined},
+ {"cancelVibrate", "(II)V", (void*)nativeCancelVibrate},
+ {"isVibrating", "(I)Z", (void*)nativeIsVibrating},
+ {"getVibratorIds", "(I)[I", (void*)nativeGetVibratorIds},
+ {"getLights", "(I)Ljava/util/List;", (void*)nativeGetLights},
+ {"getLightPlayerId", "(II)I", (void*)nativeGetLightPlayerId},
+ {"getLightColor", "(II)I", (void*)nativeGetLightColor},
+ {"setLightPlayerId", "(III)V", (void*)nativeSetLightPlayerId},
+ {"setLightColor", "(III)V", (void*)nativeSetLightColor},
+ {"getBatteryCapacity", "(I)I", (void*)nativeGetBatteryCapacity},
+ {"getBatteryStatus", "(I)I", (void*)nativeGetBatteryStatus},
+ {"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts},
+ {"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases},
+ {"dump", "()Ljava/lang/String;", (void*)nativeDump},
+ {"monitor", "()V", (void*)nativeMonitor},
+ {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled},
+ {"enableInputDevice", "(I)V", (void*)nativeEnableInputDevice},
+ {"disableInputDevice", "(I)V", (void*)nativeDisableInputDevice},
+ {"setPointerIconType", "(I)V", (void*)nativeSetPointerIconType},
+ {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons},
+ {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V",
(void*)nativeSetCustomPointerIcon},
- {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay},
- {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged},
- {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation},
- {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged},
- {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V",
+ {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
+ {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
+ {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
+ {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
+ {"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
- {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled},
- {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;",
+ {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
+ {"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
(void*)nativeGetSensorList},
- {"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor},
- {"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor},
- {"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor},
- {"nativeCancelCurrentTouch", "(J)V", (void*)nativeCancelCurrentTouch},
+ {"enableSensor", "(IIII)Z", (void*)nativeEnableSensor},
+ {"disableSensor", "(II)V", (void*)nativeDisableSensor},
+ {"flushSensor", "(II)Z", (void*)nativeFlushSensor},
+ {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
};
#define FIND_CLASS(var, className) \
@@ -2436,11 +2406,21 @@
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
int register_android_server_InputManager(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService",
- gInputManagerMethods, NELEM(gInputManagerMethods));
- (void) res; // Faked use when LOG_NDEBUG.
+ int res = jniRegisterNativeMethods(env,
+ "com/android/server/input/"
+ "NativeInputManagerService$NativeImpl",
+ gInputManagerMethods, NELEM(gInputManagerMethods));
+ (void)res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ FIND_CLASS(gNativeInputManagerServiceImpl.clazz,
+ "com/android/server/input/"
+ "NativeInputManagerService$NativeImpl");
+ gNativeInputManagerServiceImpl.clazz =
+ jclass(env->NewGlobalRef(gNativeInputManagerServiceImpl.clazz));
+ gNativeInputManagerServiceImpl.mPtr =
+ env->GetFieldID(gNativeInputManagerServiceImpl.clazz, "mPtr", "J");
+
// Callbacks
jclass clazz;
@@ -2499,9 +2479,6 @@
"dispatchUnhandledKey",
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
- GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
- "checkInjectEventsPermission", "(II)Z");
-
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index e1fe1d8..90fd8ed 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -685,11 +685,13 @@
private boolean hasNonMidiUuids(BluetoothDevice btDevice) {
ParcelUuid[] uuidParcels = btDevice.getUuids();
- // The assumption is that these services are indicative of devices that
- // ARE NOT MIDI devices.
- for (ParcelUuid parcel : uuidParcels) {
- if (mNonMidiUUIDs.contains(parcel)) {
- return true;
+ if (uuidParcels != null) {
+ // The assumption is that these services are indicative of devices that
+ // ARE NOT MIDI devices.
+ for (ParcelUuid parcel : uuidParcels) {
+ if (mNonMidiUUIDs.contains(parcel)) {
+ return true;
+ }
}
}
return false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 77cf543..c0b4f0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -671,7 +671,7 @@
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
testPid, true);
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, notificationId);
+ testPkgName, testUid, notificationId, false);
mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
testPkgName, null, notificationId, null, testUid, testPid,
new Notification(), UserHandle.of(testUser), null, mCurrentTimeMillis), null);
@@ -848,7 +848,7 @@
// Pretend we have the notification dismissed.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, -notificationId);
+ testPkgName, testUid, notificationId, true);
clearInvocations(mInjector.getAppStandbyInternal());
clearInvocations(mInjector.getNotificationManager());
clearInvocations(mBgRestrictionController);
@@ -885,7 +885,7 @@
// Pretend notification is back on.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, notificationId);
+ testPkgName, testUid, notificationId, false);
// Now we'll prompt the user even it has a FGS with notification.
bgPromptFgsWithNotiToBgRestricted.set(true);
clearInvocations(mInjector.getAppStandbyInternal());
@@ -1224,7 +1224,7 @@
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
testPid1, true);
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName1, testUid1, fgsNotificationId);
+ testPkgName1, testUid1, fgsNotificationId, false);
mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
testPkgName1, null, fgsNotificationId, null, testUid1, testPid1,
new Notification(), UserHandle.of(testUser1), null, mCurrentTimeMillis), null);
@@ -1235,7 +1235,7 @@
// Pretend we have the notification dismissed.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName1, testUid1, -fgsNotificationId);
+ testPkgName1, testUid1, fgsNotificationId, true);
// Verify we have the notification.
notificationId = checkNotificationShown(
@@ -1500,7 +1500,7 @@
if (withNotification) {
final int notificationId = 1000;
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- packageName, uid, notificationId);
+ packageName, uid, notificationId, false);
final StatusBarNotification noti = new StatusBarNotification(
packageName, null, notificationId, null, uid, pid,
new Notification(), UserHandle.of(UserHandle.getUserId(uid)),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9a4f8e2..43ba39a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -2164,6 +2164,49 @@
}
}
+ @Test
+ public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
+ setStandbyBucket(RESTRICTED_INDEX, js);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
+ // out of quota.
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ // Still in grace period
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(6 * SECOND_IN_MILLIS);
+ // Out of grace period.
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
/**
* Tests that Timers properly track sessions when an app becomes top and is closed.
*/
@@ -5559,6 +5602,111 @@
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ @Test
+ public void testEJTimerTracking_TempAllowlisting_Restricted() {
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
+ setStandbyBucket(RESTRICTED_INDEX, job);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Job starts after app is added to temp allowlist and stops before removal.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts after app is added to temp allowlist and stops after removal,
+ // before grace period ends.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
+ advanceElapsedClock(elapsedGracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+ }
+ expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ elapsedGracePeriodMs += SECOND_IN_MILLIS;
+
+ // Job starts during grace period and ends after grace period ends
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
+ advanceElapsedClock(remainingGracePeriod);
+ // Wait for handler to update Timer
+ // Can't directly evaluate the message because for some reason, the captured message returns
+ // the wrong 'what' even though the correct message goes to the handler and the correct
+ // path executes.
+ verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts and runs completely after temp allowlist grace period.
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
index cd70020..b76abe6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
@@ -84,6 +84,8 @@
private final Setting mBackgroundThrottlePackageWhitelistSetting = new Setting(
Collections.emptySet());
private final Setting mGnssMeasurementsFullTrackingSetting = new Setting(Boolean.FALSE);
+ private final Setting mAdasPackageAllowlist = new Setting(
+ new PackageTagsList.Builder().build());
private final Setting mIgnoreSettingsAllowlist = new Setting(
new PackageTagsList.Builder().build());
private final Setting mBackgroundThrottleProximityAlertIntervalSetting = new Setting(
@@ -194,10 +196,29 @@
}
@Override
+ public PackageTagsList getAdasAllowlist() {
+ return mAdasPackageAllowlist.getValue(PackageTagsList.class);
+ }
+
+ @Override
+ public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.addListener(listener);
+ }
+
+ @Override
+ public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.removeListener(listener);
+ }
+
+ @Override
public PackageTagsList getIgnoreSettingsAllowlist() {
return mIgnoreSettingsAllowlist.getValue(PackageTagsList.class);
}
+ public void setAdasSettingsAllowlist(PackageTagsList newValue) {
+ mAdasPackageAllowlist.setValue(newValue);
+ }
+
public void setIgnoreSettingsAllowlist(PackageTagsList newValue) {
mIgnoreSettingsAllowlist.setValue(newValue);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index d8f409d..71cc65b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1107,6 +1107,10 @@
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
+
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
@@ -1136,6 +1140,10 @@
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
+
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
@@ -1160,11 +1168,16 @@
@Test
public void testProviderRequest_AdasGnssBypass_ProviderDisabled_AdasDisabled() {
+ doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
+ doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(
IDENTITY.getPackageName()).build());
- doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
- doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
createManager(GPS_PROVIDER);
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index b154d6f..1171518 100644
--- a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -110,10 +110,7 @@
Thread thread = new Thread(() -> {
try {
- /*String name =*/ client.transportDirName();
- fail("transportDirName should be cancelled");
- } catch (CancellationException ex) {
- // This is expected.
+ assertThat(client.transportDirName()).isNull();
} catch (Exception ex) {
fail("unexpected Exception: " + ex.getClass().getCanonicalName());
}
@@ -189,7 +186,7 @@
}
@Test
- public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception {
+ public void testFinishBackup_canceledBeforeCompletion_returnsError() throws Exception {
TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
BackupTransportClient client = new BackupTransportClient(binder);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 18f2642..112db76 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -61,7 +61,8 @@
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -93,6 +94,7 @@
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
@@ -153,7 +155,6 @@
mHdmiControlService);
audioDevice.init();
mLocalDevices.add(audioDevice);
- mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index e4c5ad67..e4eecc6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -68,7 +68,8 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
- new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index d73cdb5..5b11466 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -68,7 +68,8 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
- new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return mAudioManager;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
new file mode 100644
index 0000000..e06877f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2021 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.hdmi;
+
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+
+import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
+ * the device responds correctly to incoming <Report Audio Status> messages and API calls
+ * from AudioService when AVC is active.
+ *
+ * This is an abstract base class. Concrete subclasses specify the type of the local device, and the
+ * type of the System Audio device. This allows each test to be run for multiple setups.
+ *
+ * We test the following pairs of (local device, System Audio device):
+ * (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
+ * (TV, Audio System): {@link TvToAudioSystemAvcTest}
+ */
+public abstract class BaseAbsoluteVolumeControlTest {
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevice mHdmiCecLocalDevice;
+ private FakeHdmiCecConfig mHdmiCecConfig;
+ private FakePowerManagerWrapper mPowerManager;
+ private Looper mLooper;
+ private Context mContextSpy;
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+ @Mock protected AudioManager mAudioManager;
+ protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
+
+ protected TestLooper mTestLooper = new TestLooper();
+ protected FakeNativeWrapper mNativeWrapper;
+
+ // Audio Status given by the System Audio device in its initial <Report Audio Status> that
+ // triggers AVC being enabled
+ private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
+ new AudioStatus(50, false);
+
+ // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
+ private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
+ .setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build();
+
+ protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
+
+ protected abstract int getPhysicalAddress();
+ protected abstract int getDeviceType();
+ protected abstract AudioDeviceAttributes getAudioOutputDevice();
+
+ protected abstract int getSystemAudioDeviceLogicalAddress();
+ protected abstract int getSystemAudioDeviceType();
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+ mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ Collections.singletonList(getDeviceType()),
+ mAudioDeviceVolumeManager) {
+ @Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+ };
+
+ mLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mLooper);
+
+ mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+ mHdmiCecConfig.setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mHdmiCecConfig.setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
+ mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS);
+
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(
+ HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.initService();
+ mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+ mHdmiControlService.setPowerManager(mPowerManager);
+
+ mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService);
+ mHdmiCecLocalDevice.init();
+ mLocalDevices.add(mHdmiCecLocalDevice);
+
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(1, true);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
+ mTestLooper.dispatchAll();
+
+ // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
+ doAnswer(invocation -> {
+ setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
+ return null;
+ }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
+
+ // Set starting volume behavior
+ doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
+ .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
+
+ // Audio service always plays STREAM_MUSIC on the device we need
+ doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
+ .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
+
+ // Max volume of STREAM_MUSIC
+ doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+ // Receive messages from devices to make sure they're registered in HdmiCecNetwork
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ Constants.ADDR_TV, getLogicalAddress()));
+ if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress()));
+ }
+
+ mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mTestLooper.dispatchAll();
+ }
+
+ protected int getLogicalAddress() {
+ synchronized (mHdmiCecLocalDevice.mLock) {
+ return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
+ }
+ }
+
+ /**
+ * Simulates the volume behavior of {@code device} being set to {@code behavior}.
+ */
+ protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int behavior) {
+ doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
+ mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Changes the setting for CEC volume.
+ */
+ protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Has the device receive a <Report Features> message from the System Audio device specifying
+ * whether <Set Audio Volume Level> is supported or not.
+ */
+ protected void receiveSetAudioVolumeLevelSupport(
+ @DeviceFeatures.FeatureSupportStatus int featureSupportStatus) {
+ // <Report Features> can't specify an unknown feature support status
+ if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) {
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE,
+ Collections.emptyList(),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(featureSupportStatus)
+ .build()));
+ mTestLooper.dispatchAll();
+ }
+ }
+
+ /**
+ * Enables System Audio mode if the System Audio device is an Audio System.
+ */
+ protected void enableSystemAudioModeIfNeeded() {
+ if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+ receiveSetSystemAudioMode(true);
+ }
+ }
+
+ /**
+ * Sets System Audio Mode by having the device receive <Set System Audio Mode>
+ * from the Audio System.
+ */
+ protected void receiveSetSystemAudioMode(boolean status) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status));
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Has the device receive a <Report Audio Status> reporting the status in
+ * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
+ */
+ protected void receiveInitialReportAudioStatus() {
+ receiveReportAudioStatus(
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
+ }
+
+ /**
+ * Has the device receive a <Report Audio Status> message from the System Audio Device.
+ */
+ protected void receiveReportAudioStatus(int volume, boolean mute) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ getSystemAudioDeviceLogicalAddress(),
+ getLogicalAddress(),
+ volume,
+ mute));
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Triggers all the conditions required to enable Absolute Volume Control.
+ */
+ protected void enableAbsoluteVolumeControl() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ enableSystemAudioModeIfNeeded();
+ receiveInitialReportAudioStatus();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+
+ /**
+ * Verifies that AVC was enabled - that is the audio output device's volume behavior was last
+ * set to absolute volume behavior.
+ */
+ protected void verifyAbsoluteVolumeEnabled() {
+ InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+ inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
+ eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+ }
+
+ /**
+ * Verifies that AVC was disabled - that is, the audio output device's volume behavior was
+ * last set to something other than absolute volume behavior.
+ */
+ protected void verifyAbsoluteVolumeDisabled() {
+ InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+ inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
+ eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+ inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ }
+
+ protected void verifyGiveAudioStatusNeverSent() {
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ protected void verifyGiveAudioStatusSent() {
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ SetAudioVolumeLevelMessage.build(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
+ Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveFeatures(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ verifyGiveAudioStatusNeverSent();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ verifyGiveAudioStatusNeverSent();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() {
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() {
+ // Only run when the System Audio device is an Audio System.
+ assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ receiveSetSystemAudioMode(true);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+
+ // Verify that AVC was never enabled
+ verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ receiveInitialReportAudioStatus();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+
+ @Test
+ public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
+ mTestLooper.dispatchAll();
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
+ // Only run when the System Audio device is an Audio System.
+ assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+ enableAbsoluteVolumeControl();
+
+ receiveSetSystemAudioMode(false);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+ // Initial <Report Audio Status> has volume=50 and mute=false
+ enableAbsoluteVolumeControl();
+
+ // New volume and mute status: sets both
+ receiveReportAudioStatus(20, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New volume only: sets volume only
+ receiveReportAudioStatus(32, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New mute status only: sets mute only
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // Repeat of earlier message: sets neither volume nor mute
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
+ // volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
+ // still cause us to call setStreamVolume()
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ .setVolumeIndex(20)
+ .build()
+ );
+ mTestLooper.dispatchAll();
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ }
+
+ @Test
+ public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
+ enableAbsoluteVolumeControl();
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+ getAudioOutputDevice(),
+ ENABLE_AVC_VOLUME_INFO,
+ AudioManager.ADJUST_RAISE,
+ AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
+ enableAbsoluteVolumeControl();
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ .setVolumeIndex(20)
+ .build()
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ SetAudioVolumeLevelMessage.build(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress(), 20));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 5cec8ad..28ba4bb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -56,7 +56,7 @@
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
HdmiControlService hdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 52a0b6c..545f318 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -78,7 +79,8 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -110,6 +112,7 @@
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 35432ed..d7fef90 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -20,6 +20,7 @@
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
@@ -100,7 +101,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -136,6 +137,7 @@
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mPowerManager = new FakePowerManagerWrapper(context);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index e77cd91..72d36b0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -20,6 +20,7 @@
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -109,7 +110,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -145,6 +146,7 @@
true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..d33ef9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
+ */
+public class FakeAudioDeviceVolumeManagerWrapper implements
+ AudioDeviceVolumeManagerWrapperInterface {
+
+ private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
+
+ public FakeAudioDeviceVolumeManagerWrapper() {
+ mVolumeBehaviorListeners = new HashSet<>();
+ }
+
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mVolumeBehaviorListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
+ mVolumeBehaviorListeners.remove(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ // Notify all volume behavior listeners that the device adopted absolute volume behavior
+ for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
+ listener.onDeviceVolumeBehaviorChanged(device,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 30bcc7e..9f744f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -89,7 +89,8 @@
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 2dcc449..0cba106 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -101,7 +101,8 @@
mMyLooper = mTestLooper.getLooper();
mHdmiControlServiceSpy = spy(new HdmiControlService(
- InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
+ InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 70bc460..91d265c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -88,7 +88,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 86130da..484b5a8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -92,7 +93,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -151,6 +152,7 @@
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index fb8baa3..48e70fe 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -17,6 +17,7 @@
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -137,7 +138,8 @@
Context context = InstrumentationRegistry.getTargetContext();
mHdmiControlService =
- new HdmiControlService(context, Collections.emptyList()) {
+ new HdmiControlService(context, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return isControlEnabled;
@@ -190,6 +192,7 @@
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x2000);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index df4aa5d..f27b8c2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -29,8 +30,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
@@ -125,7 +128,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -179,6 +182,7 @@
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -768,7 +772,7 @@
// When the device reports its physical address, the listener eventually is invoked.
HdmiCecMessage reportPhysicalAddress =
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
@@ -777,6 +781,54 @@
assertThat(mDeviceEventListeners.size()).isEqualTo(1);
assertThat(mDeviceEventListeners.get(0).getStatus())
.isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+ @Test
+ public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
+ when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+
+ // Max volume of STREAM_MUSIC is retrieved on boot
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+ ADDR_PLAYBACK_1,
+ ADDR_TV,
+ 20));
+ mTestLooper.dispatchAll();
+
+ // <Feature Abort>[Not in correct mode] not sent
+ HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_TV,
+ ADDR_PLAYBACK_1,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+
+ // <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
+ }
+
+ @Test
+ public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, ADDR_TV, true));
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+ ADDR_PLAYBACK_1, ADDR_TV, 50));
+ mTestLooper.dispatchAll();
+
+ // <Feature Abort>[Not in correct mode] sent
+ HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_TV,
+ ADDR_PLAYBACK_1,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+
+ // AudioManager not notified of volume change
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 50c9f70..a446e10 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -51,7 +51,8 @@
@Before
public void setUp() throws Exception {
HdmiControlService mHdmiControlService = new HdmiControlService(
- InstrumentationRegistry.getTargetContext(), Collections.emptyList());
+ InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper());
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 03532ae..b8a1ba3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -67,7 +67,8 @@
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
- mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListenerStatuses.add(status);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 7a68285..b94deed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -65,7 +66,8 @@
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -105,6 +107,7 @@
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(contextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 3987c32..6266571 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -91,7 +91,8 @@
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 561e6a5..46a4e86 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
@@ -87,7 +88,8 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -120,6 +122,7 @@
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
new file mode 100644
index 0000000..6418602
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is an Audio System.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDevicePlayback(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x1100;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_AUDIO_SYSTEM;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ }
+
+ /**
+ * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
+ * support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
+ * <Set Audio Volume Level> and sends <Report Audio Status>.
+ */
+ @Test
+ public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+ enableAbsoluteVolumeControl();
+
+ // Audio System disables System Audio Mode. AVC should be disabled.
+ receiveSetSystemAudioMode(false);
+ verifyAbsoluteVolumeDisabled();
+
+ // TV reports support for <Set Audio Volume Level>
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build()));
+ mTestLooper.dispatchAll();
+
+ // TV reports its initial audio status
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ Constants.ADDR_TV,
+ getLogicalAddress(),
+ 30,
+ false));
+ mTestLooper.dispatchAll();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
new file mode 100644
index 0000000..504c3bc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is a TV.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDevicePlayback(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x1100;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_TV;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_TV;
+ }
+
+ /**
+ * AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
+ * becomes the System Audio device. It is enabled once the Audio System reports that it
+ * supports <Set Audio Volume Level> and sends <Report Audio Status>.
+ */
+ @Test
+ public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+ enableAbsoluteVolumeControl();
+
+ // Audio System enables System Audio Mode. AVC should be disabled.
+ receiveSetSystemAudioMode(true);
+ verifyAbsoluteVolumeDisabled();
+
+ clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
+
+ // Audio System reports support for <Set Audio Volume Level>
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
+ Collections.emptyList(),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build()));
+ mTestLooper.dispatchAll();
+
+ // Audio system reports its initial audio status
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ Constants.ADDR_AUDIO_SYSTEM,
+ getLogicalAddress(),
+ 30,
+ false));
+ mTestLooper.dispatchAll();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index c878f99..e5058be 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
@@ -68,7 +69,8 @@
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiControlService = new HdmiControlService(mContextSpy,
- Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -110,6 +112,7 @@
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfo);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x0000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 6184c21..f7983ca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -96,8 +97,8 @@
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
- new HdmiControlService(context,
- Collections.emptyList()) {
+ new HdmiControlService(context, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -125,6 +126,7 @@
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 0587864..566a7e0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -149,7 +150,7 @@
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -186,6 +187,7 @@
true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a34b55c..087e407 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -20,6 +20,7 @@
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -81,7 +82,8 @@
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -98,6 +100,7 @@
mHdmiControlServiceSpy.setHdmiMhlController(
HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
mHdmiControlServiceSpy.initService();
+ mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 9d14341..1644252 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
@@ -69,7 +70,8 @@
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -108,6 +110,7 @@
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 095c69c..c2f706a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -69,7 +69,7 @@
Context context = InstrumentationRegistry.getTargetContext();
HdmiControlService hdmiControlService = new HdmiControlService(context,
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(
HdmiCecMessage command, @Nullable SendMessageCallback callback) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
new file mode 100644
index 0000000..41c0e0d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a TV and the System Audio device
+ * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDeviceTv(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x0000;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_TV;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_AUDIO_SYSTEM;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index bd35be4..c735d18 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -39,7 +38,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
@@ -102,8 +100,6 @@
@Mock
private Context mMockContext;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
- @Mock
private PackageManager mMockPackageManager;
@Mock
private LocaleManagerService mMockLocaleManagerService;
@@ -129,7 +125,6 @@
@Before
public void setUp() throws Exception {
mMockContext = mock(Context.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockPackageManager = mock(PackageManager.class);
mMockLocaleManagerService = mock(LocaleManagerService.class);
SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
@@ -141,7 +136,7 @@
broadcastHandlerThread.start();
mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
- mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA,
+ mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
broadcastHandlerThread));
doNothing().when(mBackupHelper).notifyBackupManager();
@@ -158,8 +153,8 @@
@Test
public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
}
@@ -198,8 +193,8 @@
ApplicationInfo anotherAppInfo = new ApplicationInfo();
defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
anotherAppInfo.packageName = "com.android.anotherapp";
- doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
// Exception when getting locales for anotherApp.
@@ -447,8 +442,8 @@
// Retention period has not elapsed.
setCurrentTimeMillis(
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataExists(DEFAULT_USER_ID);
@@ -456,8 +451,8 @@
// Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
// removed.
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataExists(DEFAULT_USER_ID);
@@ -465,8 +460,8 @@
// Retention period has now expired, stage data should be deleted.
setCurrentTimeMillis(
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -577,8 +572,8 @@
private void setUpDummyAppForPackageManager(String packageName) {
ApplicationInfo dummyApp = new ApplicationInfo();
dummyApp.packageName = packageName;
- doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of(dummyApp)).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 0b3ef45..1dcdbac 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -23,7 +23,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -40,7 +39,6 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.LocaleList;
@@ -80,7 +78,7 @@
@Mock
private Context mMockContext;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
+ private PackageManager mMockPackageManager;
@Mock
private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
@Mock
@@ -95,14 +93,13 @@
mMockContext = mock(Context.class);
mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
mMockActivityManager = mock(ActivityManagerInternal.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+ mMockPackageManager = mock(PackageManager.class);
mMockPackageMonitor = mock(PackageMonitor.class);
// For unit tests, set the default installer info
- PackageManager mockPackageManager = mock(PackageManager.class);
- doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager)
+ doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
- doReturn(mockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
doReturn(mFakePackageConfigurationUpdater)
@@ -117,14 +114,14 @@
mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
- mMockActivityManager, mMockPackageManagerInternal,
+ mMockActivityManager, mMockPackageManager,
mMockBackupHelper, mMockPackageMonitor);
}
@Test(expected = SecurityException.class)
public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
doReturn(DEFAULT_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
try {
@@ -170,7 +167,7 @@
@Test
public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
doReturn(DEFAULT_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
// if package is not owned by the caller, the calling app should have the following
// permission. We will mock this to succeed to imitate that.
setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
@@ -186,7 +183,7 @@
@Test
public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
doReturn(Binder.getCallingUid())
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
DEFAULT_LOCALES);
@@ -197,8 +194,8 @@
@Test(expected = IllegalArgumentException.class)
public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
- doReturn(INVALID_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ doThrow(new PackageManager.NameNotFoundException("Mock"))
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
LocaleList.getEmptyLocaleList());
@@ -211,8 +208,8 @@
@Test(expected = SecurityException.class)
public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
try {
@@ -229,8 +226,8 @@
public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
throws Exception {
// any valid app calling for its own package or having appropriate permission
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(null)
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -244,8 +241,8 @@
@Test
public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
.when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
@@ -259,8 +256,8 @@
@Test
public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
throws Exception {
- doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -273,8 +270,8 @@
@Test
public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -288,10 +285,10 @@
@Test
public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt());
- doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
- .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index ad9be0d..e403c87 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,7 +17,7 @@
package com.android.server.locales;
import android.content.Context;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager;
import android.os.HandlerThread;
import android.util.SparseArray;
@@ -31,9 +31,10 @@
public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
ShadowLocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, Clock clock,
+ PackageManager packageManager, Clock clock,
SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
HandlerThread broadcastHandlerThread) {
- super(context, localeManagerService, pmInternal, clock, stagedData, broadcastHandlerThread);
+ super(context, localeManagerService, packageManager, clock, stagedData,
+ broadcastHandlerThread);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index db602ca..808b74e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -38,7 +38,6 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Environment;
import android.os.LocaleList;
@@ -93,8 +92,6 @@
@Mock
PackageManager mMockPackageManager;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
- @Mock
private ActivityTaskManagerInternal mMockActivityTaskManager;
@Mock
private ActivityManagerInternal mMockActivityManager;
@@ -110,21 +107,20 @@
mMockContext = mock(Context.class);
mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
mMockActivityManager = mock(ActivityManagerInternal.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+ mMockPackageManager = mock(PackageManager.class);
LocaleManagerBackupHelper mockLocaleManagerBackupHelper =
mock(ShadowLocaleManagerBackupHelper.class);
// PackageMonitor is not needed in LocaleManagerService for these tests hence it is
// passed as null.
mLocaleManagerService = new LocaleManagerService(mMockContext,
mMockActivityTaskManager, mMockActivityManager,
- mMockPackageManagerInternal, mockLocaleManagerBackupHelper,
+ mMockPackageManager, mockLocaleManagerBackupHelper,
/* mPackageMonitor= */ null);
doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
.handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
anyString(), anyString());
- mMockPackageManager = mock(PackageManager.class);
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 004d7bc..07cca0c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -84,15 +84,15 @@
@RunWith(AndroidJUnit4.class)
public class PackageParserLegacyCoreTest {
private static final String RELEASED = null;
- private static final String OLDER_PRE_RELEASE = "A";
- private static final String PRE_RELEASE = "B";
- private static final String NEWER_PRE_RELEASE = "C";
+ private static final String OLDER_PRE_RELEASE = "Q";
+ private static final String PRE_RELEASE = "R";
+ private static final String NEWER_PRE_RELEASE = "Z";
// Codenames with a fingerprint attached to them. These may only be present in the apps
// declared min SDK and not as platform codenames.
- private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "A.fingerprint";
- private static final String PRE_RELEASE_WITH_FINGERPRINT = "B.fingerprint";
- private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "C.fingerprint";
+ private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
+ private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
+ private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
private static final String[] CODENAMES_RELEASED = { /* empty */};
private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
@@ -199,13 +199,14 @@
}
private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
- boolean isPlatformReleased, int expectedTargetSdk) {
+ boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
targetSdkVersion,
targetSdkCodename,
isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
- input);
+ input,
+ allowUnknownCodenames);
if (expectedTargetSdk == -1) {
assertTrue(result.isError());
@@ -220,40 +221,61 @@
// Do allow older release targetSdkVersion on pre-release platform.
// APP: Released API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
// Do allow same release targetSdkVersion on pre-release platform.
// APP: Released API 20
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
// Do allow newer release targetSdkVersion on pre-release platform.
// APP: Released API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
// Don't allow older pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ false, -1
+ );
+ // Don't allow older pre-release targetSdkVersion on pre-release platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 10
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
+ true, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ true, -1);
// Do allow same pre-release targetSdkVersion on pre-release platform,
// but overwrite the specified version with CUR_DEVELOPMENT.
// APP: Pre-release API 20
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
+ false, Build.VERSION_CODES.CUR_DEVELOPMENT);
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
-
+ false, Build.VERSION_CODES.CUR_DEVELOPMENT);
// Don't allow newer pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ false, -1
+ );
+
+ // Do allow newer pre-release targetSdkVersion on pre-release platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 30
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
}
@Test
@@ -261,36 +283,58 @@
// Do allow older release targetSdkVersion on released platform.
// APP: Released API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
// Do allow same release targetSdkVersion on released platform.
// APP: Released API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
// Do allow newer release targetSdkVersion on released platform.
// APP: Released API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
// Don't allow older pre-release targetSdkVersion on released platform.
// APP: Pre-release API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ false, -1
+ );
// Don't allow same pre-release targetSdkVersion on released platform.
// APP: Pre-release API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
+ -1
+ );
+ // Don't allow same pre-release targetSdkVersion on released platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 20
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
+ -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
+ -1);
// Don't allow newer pre-release targetSdkVersion on released platform.
// APP: Pre-release API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ false, -1
+ );
+ // Do allow newer pre-release targetSdkVersion on released platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 30
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 6bdd88c..2d0755d 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -20,18 +20,15 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -56,32 +53,20 @@
import org.junit.runners.Parameterized;
import org.mockito.ArgumentCaptor;
-import java.util.LinkedList;
-import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(Parameterized.class)
public class SoundHw2CompatTest {
- @Parameterized.Parameter(0) public String mVersion;
- @Parameterized.Parameter(1) public boolean mSupportConcurrentCapture;
+ @Parameterized.Parameter public String mVersion;
private final Runnable mRebootRunnable = mock(Runnable.class);
private ISoundTriggerHal mCanonical;
- private CaptureStateNotifier mCaptureStateNotifier;
private android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
// We run the test once for every version of the underlying driver.
- @Parameterized.Parameters(name = "{0}, concurrent={1}")
- public static Iterable<Object[]> data() {
- List<Object[]> result = new LinkedList<>();
-
- for (String version : new String[]{"V2_0", "V2_1", "V2_2", "V2_3",}) {
- for (boolean concurrentCapture : new boolean[]{false, true}) {
- result.add(new Object[]{version, concurrentCapture});
- }
- }
-
- return result;
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new String[]{"V2_0", "V2_1", "V2_2", "V2_3"};
}
@Before
@@ -139,7 +124,7 @@
when(mHalDriver.asBinder()).thenReturn(binder);
android.hardware.soundtrigger.V2_3.Properties halProperties =
- TestUtil.createDefaultProperties_2_3(mSupportConcurrentCapture);
+ TestUtil.createDefaultProperties_2_3();
doAnswer(invocation -> {
((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
0)).onValues(0, halProperties.base);
@@ -156,10 +141,7 @@
}).when(driver).getProperties_2_3(any());
}
- mCaptureStateNotifier = spy(new CaptureStateNotifier());
-
- mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable,
- mCaptureStateNotifier);
+ mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable, null);
// During initialization any method can be called, but after we're starting to enforce that
// no additional methods are called.
@@ -171,7 +153,6 @@
mCanonical.detach();
verifyNoMoreInteractions(mHalDriver);
verifyNoMoreInteractions(mRebootRunnable);
- mCaptureStateNotifier.verifyNoMoreListeners();
}
@Test
@@ -194,12 +175,12 @@
// It is OK for the SUT to cache the properties, so the underlying method doesn't
// need to be called every single time.
verify(driver, atMost(1)).getProperties_2_3(any());
- TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture);
+ TestUtil.validateDefaultProperties(properties);
} else {
// It is OK for the SUT to cache the properties, so the underlying method doesn't
// need to be called every single time.
verify(mHalDriver, atMost(1)).getProperties(any());
- TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture, 0, "");
+ TestUtil.validateDefaultProperties(properties, 0, "");
}
}
@@ -291,7 +272,7 @@
ISoundTriggerHal.ModelCallback canonicalCallback = mock(
ISoundTriggerHal.ModelCallback.class);
- final int maxModels = TestUtil.createDefaultProperties_2_0(false).maxSoundModels;
+ final int maxModels = TestUtil.createDefaultProperties_2_0().maxSoundModels;
int[] modelHandles = new int[maxModels];
// Load as many models as we're allowed.
@@ -318,7 +299,7 @@
verify(globalCallback).onResourcesAvailable();
}
- private int loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
+ private void loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
throws Exception {
final int handle = 29;
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
@@ -345,10 +326,9 @@
TestUtil.validatePhraseSoundModel_2_0(modelCaptor.getValue());
validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
- return handle;
}
- private int loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
+ private void loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
throws Exception {
final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -380,14 +360,13 @@
TestUtil.validatePhraseSoundModel_2_1(model.get());
validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
- return handle;
}
- public int loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+ public void loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
- return loadPhraseModel_2_1(canonicalCallback);
+ loadPhraseModel_2_1(canonicalCallback);
} else {
- return loadPhraseModel_2_0(canonicalCallback);
+ loadPhraseModel_2_0(canonicalCallback);
}
}
@@ -484,80 +463,6 @@
}
@Test
- public void testConcurrentCaptureAbort() throws Exception {
- assumeFalse(mSupportConcurrentCapture);
- verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
- // Register global callback.
- ISoundTriggerHal.GlobalCallback globalCallback = mock(
- ISoundTriggerHal.GlobalCallback.class);
- mCanonical.registerCallback(globalCallback);
-
- // Load.
- ISoundTriggerHal.ModelCallback canonicalCallback = mock(
- ISoundTriggerHal.ModelCallback.class);
- final int handle = loadGenericModel(canonicalCallback);
-
- // Then start.
- startRecognition(handle, canonicalCallback);
-
- // Now activate external capture.
- mCaptureStateNotifier.setState(true);
-
- // Expect hardware to have been stopped.
- verify(mHalDriver).stopRecognition(handle);
-
- // Expect an abort event (async).
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- mCanonical.flushCallbacks();
- verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
-
- // Deactivate external capture.
- mCaptureStateNotifier.setState(false);
-
- // Expect a onResourcesAvailable().
- mCanonical.flushCallbacks();
- verify(globalCallback).onResourcesAvailable();
- }
-
- @Test
- public void testConcurrentCaptureReject() throws Exception {
- assumeFalse(mSupportConcurrentCapture);
- verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
- // Register global callback.
- ISoundTriggerHal.GlobalCallback globalCallback = mock(
- ISoundTriggerHal.GlobalCallback.class);
- mCanonical.registerCallback(globalCallback);
-
- // Load (this registers the callback).
- ISoundTriggerHal.ModelCallback canonicalCallback = mock(
- ISoundTriggerHal.ModelCallback.class);
- final int handle = loadGenericModel(canonicalCallback);
-
- // Report external capture active.
- mCaptureStateNotifier.setState(true);
-
- // Then start.
- RecognitionConfig config = TestUtil.createRecognitionConfig();
- try {
- mCanonical.startRecognition(handle, 203, 204, config);
- fail("Expected an exception");
- } catch (RecoverableException e) {
- assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
- }
-
- // Deactivate external capture.
- mCaptureStateNotifier.setState(false);
-
- // Expect a onResourcesAvailable().
- mCanonical.flushCallbacks();
- verify(globalCallback).onResourcesAvailable();
- }
-
- @Test
public void testStopRecognition() throws Exception {
mCanonical.stopRecognition(17);
verify(mHalDriver).stopRecognition(17);
@@ -675,7 +580,7 @@
}
@Test
- public void testGlobalCallback() throws Exception {
+ public void testGlobalCallback() {
testGlobalCallback_2_0();
}
@@ -803,29 +708,4 @@
verifyNoMoreInteractions(canonicalCallback);
clearInvocations(canonicalCallback);
}
-
- public static class CaptureStateNotifier implements ICaptureStateNotifier {
- private final List<Listener> mListeners = new LinkedList<>();
-
- @Override
- public boolean registerListener(Listener listener) {
- mListeners.add(listener);
- return false;
- }
-
- @Override
- public void unregisterListener(Listener listener) {
- mListeners.remove(listener);
- }
-
- public void setState(boolean state) {
- for (Listener listener : mListeners) {
- listener.onCaptureStateChange(state);
- }
- }
-
- public void verifyNoMoreListeners() {
- assertEquals(0, mListeners.size());
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
new file mode 100644
index 0000000..6198925
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerHalConcurrentCaptureHandlerTest {
+ private ISoundTriggerHal mUnderlying;
+ private CaptureStateNotifier mNotifier;
+ private ISoundTriggerHal.GlobalCallback mGlobalCallback;
+ private SoundTriggerHalConcurrentCaptureHandler mHandler;
+
+ @Before
+ public void setUp() {
+ mNotifier = new CaptureStateNotifier();
+ mUnderlying = mock(ISoundTriggerHal.class);
+ mGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class);
+ mHandler = new SoundTriggerHalConcurrentCaptureHandler(mUnderlying, mNotifier);
+ mHandler.registerCallback(mGlobalCallback);
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ mNotifier.setActive(true);
+ verify(mUnderlying).stopRecognition(handle);
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ Thread.sleep(50);
+ verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent event = eventCaptor.getValue();
+ assertEquals(event.status, RecognitionStatus.ABORTED);
+ assertFalse(event.recognitionStillActive);
+ verifyZeroInteractions(mGlobalCallback);
+ clearInvocations(callback, mUnderlying);
+
+ mNotifier.setActive(false);
+ Thread.sleep(50);
+ verify(mGlobalCallback).onResourcesAvailable();
+ verifyNoMoreInteractions(callback, mUnderlying);
+
+ mNotifier.setActive(true);
+ verifyNoMoreInteractions(callback, mUnderlying);
+ }
+
+ @Test
+ public void testStopBeforeActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+ mHandler.stopRecognition(handle);
+ verify(mUnderlying).stopRecognition(handle);
+ clearInvocations(mUnderlying);
+
+ mNotifier.setActive(true);
+ Thread.sleep(50);
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ public void testStopAfterActive() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ verify(callback, times(1)).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testAbortWhileStop() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+ false);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+ }
+
+ @Test(timeout = 200)
+ public void testActiveWhileStop() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ // The stop request causes a callback to be flushed.
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ // While the HAL is processing the stop request, capture state becomes active.
+ new Thread(() -> mNotifier.setActive(true)).start();
+ Thread.sleep(50);
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ // We only expect one underlying invocation of stop().
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ // The callback shouldn't be invoked in this case.
+ verify(callback, never()).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testStopWhileActive() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ // The stop request causes a callback to be flushed.
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ // While the HAL is processing the stop request, client requests stop.
+ new Thread(() -> mHandler.stopRecognition(handle)).start();
+ Thread.sleep(50);
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ // We only expect one underlying invocation of stop().
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ verify(callback, atMost(1)).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testEventWhileActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+ false);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ Thread.sleep(50);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent lastEvent = eventCaptor.getValue();
+ assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+ assertFalse(lastEvent.recognitionStillActive);
+ }
+
+
+ @Test(timeout = 200)
+ public void testNonFinalEventWhileActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ Thread.sleep(50);
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent lastEvent = eventCaptor.getValue();
+ assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+ assertFalse(lastEvent.recognitionStillActive);
+ }
+
+ private static void runOnSeparateThread(Runnable runnable) {
+ Thread thread = new Thread(runnable);
+ thread.start();
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class CaptureStateNotifier implements ICaptureStateNotifier {
+ boolean mActive = false;
+ Listener mListener;
+
+ @Override
+ public boolean registerListener(@NonNull Listener listener) {
+ mListener = listener;
+ return mActive;
+ }
+
+ @Override
+ public void unregisterListener(@NonNull Listener listener) {
+ mListener = null;
+ }
+
+ public void setActive(boolean active) {
+ mActive = active;
+ if (mListener != null) {
+ // Call the callback from a different thread to detect deadlocks by preventing
+ // recursive locking from working.
+ runOnSeparateThread(() -> mListener.onCaptureStateChange(mActive));
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 0187e34..3bebc94 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -134,7 +134,7 @@
public void setUp() throws Exception {
clearInvocations(mHalDriver);
clearInvocations(mAudioSessionProvider);
- when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties(false));
+ when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties());
mService = new SoundTriggerMiddlewareImpl(() -> mHalDriver, mAudioSessionProvider);
}
@@ -156,7 +156,7 @@
assertEquals(1, allDescriptors.length);
Properties properties = allDescriptors[0].properties;
- assertEquals(TestUtil.createDefaultProperties(false), properties);
+ assertEquals(TestUtil.createDefaultProperties(), properties);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 30b4a59..39561f7 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -162,8 +162,8 @@
phrases.get(0).recognitionModes);
}
- static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties_2_0(
- boolean supportConcurrentCapture) {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties
+ createDefaultProperties_2_0() {
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
properties.implementor = "implementor";
@@ -185,17 +185,16 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
- properties.concurrentCapture = supportConcurrentCapture;
+ properties.concurrentCapture = true;
properties.triggerInEvent = true;
properties.powerConsumptionMw = 432;
return properties;
}
- static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
- boolean supportConcurrentCapture) {
+ static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3() {
android.hardware.soundtrigger.V2_3.Properties properties =
new android.hardware.soundtrigger.V2_3.Properties();
- properties.base = createDefaultProperties_2_0(supportConcurrentCapture);
+ properties.base = createDefaultProperties_2_0();
properties.supportedModelArch = "supportedModelArch";
properties.audioCapabilities =
android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
@@ -203,7 +202,7 @@
return properties;
}
- static Properties createDefaultProperties(boolean supportConcurrentCapture) {
+ static Properties createDefaultProperties() {
Properties properties = new Properties();
properties.implementor = "implementor";
properties.description = "description";
@@ -217,7 +216,7 @@
| RecognitionMode.USER_AUTHENTICATION | RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
- properties.concurrentCapture = supportConcurrentCapture;
+ properties.concurrentCapture = true;
properties.triggerInEvent = true;
properties.powerConsumptionMw = 432;
properties.supportedModelArch = "supportedModelArch";
@@ -226,13 +225,13 @@
return properties;
}
- static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture) {
- validateDefaultProperties(properties, supportConcurrentCapture,
+ static void validateDefaultProperties(Properties properties) {
+ validateDefaultProperties(properties,
AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
"supportedModelArch");
}
- static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture,
+ static void validateDefaultProperties(Properties properties,
@AudioCapabilities int audioCapabilities, @NonNull String supportedModelArch) {
assertEquals("implementor", properties.implementor);
assertEquals("description", properties.description);
@@ -246,7 +245,7 @@
properties.recognitionModes);
assertTrue(properties.captureTransition);
assertEquals(321, properties.maxBufferMs);
- assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertEquals(true, properties.concurrentCapture);
assertTrue(properties.triggerInEvent);
assertEquals(432, properties.powerConsumptionMw);
assertEquals(supportedModelArch, properties.supportedModelArch);
diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS
index c1221e3..d40ff56 100644
--- a/tests/FlickerTests/OWNERS
+++ b/tests/FlickerTests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
include /services/core/java/com/android/server/wm/OWNERS
natanieljr@google.com
pablogamito@google.com
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index db2e645..e2e1ae8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -114,7 +114,7 @@
}
}
- @Presubmit
+ @FlakyTest(bugId = 228011606)
@Test
fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
testSpec.assertLayersStart {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 7e3ed82..4b268a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -32,12 +32,15 @@
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,11 +58,16 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
@Presubmit
-class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
+open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = SimpleAppHelper(instrumentation)
private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
similarity index 76%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 2252a94..edd52b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.server.wm.flicker.ime
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,22 +24,26 @@
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import org.junit.Assume
import org.junit.Before
+
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+/**
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+@FlakyTest(bugId = 228012334)
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter)
+ : SwitchImeWindowsFromGestureNavTest(testSpec) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
}
-
- @FlakyTest(bugId = 227214914)
- override fun pipLayerRotates_StartingBounds() = super.pipLayerRotates_StartingBounds()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index ee0f3d8..6e33f66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -26,12 +26,9 @@
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,10 +61,6 @@
@Group1
open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
: OpenAppFromLauncherTransition(testSpec) {
- @Before
- open fun before() {
- assumeFalse(isShellTransitionsEnabled)
- }
/**
* Defines the transition used to run the test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
deleted file mode 100644
index 55e1e9b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app from the recents app view (the overview)
- *
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
- *
- * Actions:
- * Launch [testApp]
- * Press recents
- * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
- * complete (only this action is traced)
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter)
- : OpenAppFromOverviewTest(testSpec) {
- @Before
- override fun before() {
- assumeTrue(isShellTransitionsEnabled)
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 216266712)
- @Test
- override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 216266712)
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() =
- super.appWindowReplacesLauncherAsTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 218470989)
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index fbd611a..f357177 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -27,10 +27,7 @@
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,11 +61,6 @@
override val testApp = NonResizeableAppHelper(instrumentation)
private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
/**
* Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
* and remains visible at the end
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4313b8d..02c1a10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.launch
import android.app.Instrumentation
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -216,7 +215,7 @@
* Checks that [testApp] window is not on top at the start of the transition, and then becomes
* the top visible window until the end of the transition.
*/
- @FlakyTest(bugId = 203538234)
+ @Presubmit
@Test
open fun appWindowBecomesTopWindow() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 1eb3d8d..c89e6a4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -33,12 +33,15 @@
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,6 +73,11 @@
private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
similarity index 64%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 8a08d07..b9fef08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
@@ -30,30 +30,24 @@
import org.junit.runners.Parameterized
/**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
*
* Actions:
- * Lock the device.
- * Launch an app on top of the lock screen [testApp] and wait animation to complete
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
*
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
- : OpenAppNonResizeableTest(testSpec) {
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter)
+ : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5474a42..725d2c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -32,6 +32,7 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,6 +40,8 @@
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,6 +71,11 @@
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
similarity index 62%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 8a08d07..9c9dedc2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -30,30 +29,23 @@
import org.junit.runners.Parameterized
/**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
*
* Actions:
- * Lock the device.
- * Launch an app on top of the lock screen [testApp] and wait animation to complete
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
- : OpenAppNonResizeableTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter)
+ : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index f83ae87..cc4a4b2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -28,7 +28,7 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
-import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -60,7 +60,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
+@Group1
class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val taplInstrumentation = LauncherInstrumentation()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 2b944c6..8b851e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
@@ -100,7 +98,7 @@
* Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
* focus returns to [testApp]
*/
- @FlakyTest(bugId = 190185577)
+ @Presubmit
@Test
fun focusChanges() {
testSpec.assertEventLog {
@@ -130,18 +128,6 @@
@Presubmit
@Test
fun rotationLayerAppearsAndVanishes() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- rotationLayerAppearsAndVanishesAssertion()
- }
-
- /**
- * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
- * doesn't flicker, and disappears before the transition is complete
- */
- @FlakyTest(bugId = 218484127)
- @Test
- fun rotationLayerAppearsAndVanishes_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
rotationLayerAppearsAndVanishesAssertion()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 15fd5e1..fac5baf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,11 +26,8 @@
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -84,11 +81,6 @@
) : RotationTransition(testSpec) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
deleted file mode 100644
index d397d59..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.rotation
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an app and cycling through app rotations using seamless rotations
- *
- * Currently runs:
- * 0 -> 90 degrees
- * 0 -> 90 degrees (with starved UI thread)
- * 90 -> 0 degrees
- * 90 -> 0 degrees (with starved UI thread)
- *
- * Actions:
- * Launch an app in fullscreen and supporting seamless rotation (via intent)
- * Set initial device orientation
- * Start tracing
- * Change device orientation
- * Stop tracing
- *
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
- *
- * To run only the presubmit assertions add: `--
- * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
- * To run only the postsubmit assertions add: `--
- * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
- * To run only the flaky assertions add: `--
- * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [RotationTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-@FlakyTest(bugId = 219689723)
-class SeamlessAppRotationTest_ShellTransit(
- testSpec: FlickerTestParameter
-) : SeamlessAppRotationTest(testSpec) {
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-}