Merge "Improved data fail cause debug message" into tm-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index b4238c9..c8ec894 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1208,7 +1208,7 @@
}
if (highestBiasJob != null) {
if (DEBUG) {
- Slog.d(TAG, "Running job " + jobStatus + " as preemption");
+ Slog.d(TAG, "Running job " + highestBiasJob + " as preemption");
}
mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
startJobLocked(worker, highestBiasJob, highBiasWorkType);
@@ -1219,7 +1219,7 @@
worker.clearPreferredUid();
if (backupJob != null) {
if (DEBUG) {
- Slog.d(TAG, "Running job " + jobStatus + " instead");
+ Slog.d(TAG, "Running job " + backupJob + " instead");
}
mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
startJobLocked(worker, backupJob, backupWorkType);
@@ -1263,7 +1263,7 @@
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
if (DEBUG) {
- Slog.d(TAG, "About to run job: " + jobStatus);
+ Slog.d(TAG, "About to run job: " + highestBiasJob);
}
mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
startJobLocked(worker, highestBiasJob, highBiasWorkType);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b25832c..25db58e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1745,7 +1745,13 @@
// Remove from store as well as controllers.
final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
- if (removed && mReadyToRock) {
+ if (!removed) {
+ // We never create JobStatus objects for the express purpose of removing them, and this
+ // method is only ever called for jobs that were saved in the JobStore at some point,
+ // so if we can't find it, something went seriously wrong.
+ Slog.wtfStack(TAG, "Job didn't exist in JobStore");
+ }
+ if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 2a79ec4..f7fe9ca 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -992,6 +992,10 @@
if (mVerb == VERB_FINISHED) {
return;
}
+ if (DEBUG) {
+ Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString()
+ + " reschedule=" + reschedule + " reason=" + reason);
+ }
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
final int internalStopReason = mParams.getInternalStopReasonCode();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index daf1ee1..0eacfd6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -261,6 +261,9 @@
}
final JobStatus job1 = aj1.job;
final JobStatus job2 = aj2.job;
+ if (job1 == job2) {
+ return 0;
+ }
// Jobs with an override state set (via adb) should be put first as tests/developers
// expect the jobs to run immediately.
if (job1.overrideState != job2.overrideState) {
@@ -381,18 +384,18 @@
return indexOf(job) >= 0;
}
+ /** Returns the current index of the job, or -1 if the job isn't in the list. */
private int indexOf(@NonNull JobStatus jobStatus) {
- AdjustedJobStatus adjustedJobStatus = mAdjustedJobStatusPool.acquire();
- if (adjustedJobStatus == null) {
- adjustedJobStatus = new AdjustedJobStatus();
+ // Binary search can't guarantee returning the correct index
+ // if there are multiple jobs whose sorting comparison are 0, so we need to iterate
+ // through the entire list.
+ for (int i = 0, size = mJobs.size(); i < size; ++i) {
+ AdjustedJobStatus adjustedJobStatus = mJobs.get(i);
+ if (adjustedJobStatus.job == jobStatus) {
+ return i;
+ }
}
- adjustedJobStatus.adjustedEnqueueTime = jobStatus.enqueueTime;
- adjustedJobStatus.job = jobStatus;
-
- int where = Collections.binarySearch(mJobs, adjustedJobStatus, sJobComparator);
- adjustedJobStatus.clear();
- mAdjustedJobStatusPool.release(adjustedJobStatus);
- return where;
+ return -1;
}
@Nullable
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index 735df80..af00f31 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -262,7 +262,7 @@
* interaction.
*/
public void requestTouchExploration() {
- checkState();
+ validateTransitionRequest();
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance()
.getConnection(mService.getConnectionId());
@@ -288,7 +288,7 @@
* @throws IllegalArgumentException if the pointer id is outside of the allowed range.
*/
public void requestDragging(int pointerId) {
- checkState();
+ validateTransitionRequest();
if (pointerId < 0 || pointerId > MAX_POINTER_COUNT) {
throw new IllegalArgumentException("Invalid pointer id: " + pointerId);
}
@@ -313,7 +313,7 @@
* the duration of this interaction.
*/
public void requestDelegating() {
- checkState();
+ validateTransitionRequest();
final IAccessibilityServiceConnection connection =
AccessibilityInteractionClient.getInstance()
.getConnection(mService.getConnectionId());
@@ -371,14 +371,14 @@
}
}
- private void checkState() {
+ private void validateTransitionRequest() {
if (!mServiceDetectsGestures || mCallbacks.size() == 0) {
throw new IllegalStateException(
"State transitions are not allowed without first adding a callback.");
}
- if (mState != STATE_TOUCH_INTERACTING && mState != STATE_DRAGGING) {
+ if ((mState == STATE_DELEGATING || mState == STATE_TOUCH_EXPLORING)) {
throw new IllegalStateException(
- "State transitions are not allowed in " + stateToString(mState));
+ "State transition requests are not allowed in " + stateToString(mState));
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9c391ab..7255a13 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -239,6 +239,12 @@
public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
/**
+ * Inform ActivityManagerService about the latest {@code blockedReasons} for an uid, which
+ * can be used to understand whether the {@code uid} is allowed to access network or not.
+ */
+ public abstract void onUidBlockedReasonsChanged(int uid, int blockedReasons);
+
+ /**
* @return true if runtime was restarted, false if it's normal boot
*/
public abstract boolean isRuntimeRestarted();
@@ -562,15 +568,14 @@
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
/**
- * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
- * targets the given uid.
+ * Checks if there is an unfinished instrumentation that targets the given uid.
*
* @param uid The uid to be checked for
*
- * @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.
+ * @return True, if there is an instrumentation whose target application uid matches the given
+ * uid, false otherwise
*/
- public abstract int getInstrumentationSourceUid(int uid);
+ public abstract boolean isUidCurrentlyInstrumented(int uid);
/** Is this a device owner app? */
public abstract boolean isDeviceOwner(int uid);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 961135f..99d7c63 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,11 +1058,10 @@
}
/**
- * 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)
+ * Sends the key events corresponding to the text to the app being
+ * instrumented.
+ *
+ * @param text The text to be sent.
*/
public void sendStringSync(String text) {
if (text == null) {
@@ -1085,12 +1084,11 @@
}
/**
- * 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.
+ * 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.
*
* @param event The event to send to the current focus.
*/
@@ -1118,42 +1116,34 @@
}
/**
- * Sends up and down key events with the given key code to the currently focused window, and
- * waits for it to be processed.
+ * Sends an up and down key event sync to the currently focused window.
*
- * @param keyCode The key code for the events to send.
- * @see #sendKeySync(KeyEvent)
+ * @param key The integer keycode for the event.
*/
- public void sendKeyDownUpSync(int keyCode) {
+ 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) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
-
+
/**
- * 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.
+ * 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.
*
* @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
@@ -1165,10 +1155,10 @@
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- syncInputTransactionsAndInjectEventIntoSelf(event);
+ syncInputTransactionsAndInjectEvent(event);
}
- private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
+ private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1179,9 +1169,8 @@
.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, Process.myUid());
+ event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
if (syncAfter) {
WindowManagerGlobal.getWindowManagerService()
@@ -1193,21 +1182,19 @@
}
/**
- * 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.
- *
+ * 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.
+ *
* @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.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 649f904..3c64b99 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,6 +110,19 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
],
"file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 57e84bd..e1ffd4a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,11 +57,10 @@
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
- // 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.
+ // Injects an input event into the system. To inject into windows owned by other
+ // applications, the caller must have the INJECT_EVENTS permission.
@UnsupportedAppUsage
- boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
+ boolean injectInputEvent(in InputEvent ev, int mode);
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0bcabdd..cc5b275 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,7 +45,6 @@
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;
@@ -1108,18 +1107,14 @@
}
}
+
/**
- * 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.
- *
+ * 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 the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+ * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
+ * windows that are owned by other applications.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
@@ -1130,14 +1125,12 @@
* {@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) {
+ @UnsupportedAppUsage
+ public boolean injectInputEvent(InputEvent event, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
@@ -1148,39 +1141,13 @@
}
try {
- return mIm.injectInputEvent(event, mode, targetUid);
+ return mIm.injectInputEvent(event, mode);
} 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 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}.
- * @return True if input event injection succeeded.
- *
- * @hide
- */
- @RequiresPermission(Manifest.permission.INJECT_EVENTS)
- @UnsupportedAppUsage
- public boolean injectInputEvent(InputEvent event, int mode) {
- return injectInputEvent(event, mode, Process.INVALID_UID);
- }
-
- /**
* Verify the details of an {@link android.view.InputEvent} that came from the system.
* If the event did not come from the system, or its details could not be verified, then this
* will return {@code null}. Receiving {@code null} does not mean that the event did not
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 2c2a703..9341105 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -817,11 +817,13 @@
public static final class UidState {
public int uid;
public int procState;
+ public long procStateSeq;
public int capability;
- public UidState(int uid, int procState, int capability) {
+ public UidState(int uid, int procState, long procStateSeq, int capability) {
this.uid = uid;
this.procState = procState;
+ this.procStateSeq = procStateSeq;
this.capability = capability;
}
@@ -830,6 +832,8 @@
final StringBuilder sb = new StringBuilder();
sb.append("{procState=");
sb.append(procStateToString(procState));
+ sb.append(",seq=");
+ sb.append(procStateSeq);
sb.append(",cap=");
ActivityManager.printCapabilitiesSummary(sb, capability);
sb.append("}");
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index 1270d87..d3cc918 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -110,6 +110,13 @@
@WindowConfiguration.WindowingMode int windowingMode);
/**
+ * Returns {@code true} if the given new task can be launched on this virtual display.
+ */
+ public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
+ @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+ boolean isNewTask);
+
+ /**
* Called when an Activity window is layouted with the new changes where contains the
* window flags that we’re interested in.
* Returns {@code false} if the Activity cannot remain on the display and the activity task will
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index 2d6c77f..ca111a4 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -36,6 +36,7 @@
private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
+ private static final String BLOCKED_COMPONENT_SETTINGS = "com.android.settings";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,13 +57,24 @@
CharSequence streamedDeviceName = intent.getCharSequenceExtra(EXTRA_STREAMED_DEVICE);
if (!TextUtils.isEmpty(streamedDeviceName)) {
- mAlertParams.mTitle =
- TextUtils.equals(activityInfo.packageName,
- getPackageManager().getPermissionControllerPackageName())
- ? getString(R.string.app_streaming_blocked_title_for_permission_dialog)
- : getString(R.string.app_streaming_blocked_title, appLabel);
- mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ if (TextUtils.equals(activityInfo.packageName,
+ getPackageManager().getPermissionControllerPackageName())) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_permission_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message_for_permission_dialog,
+ streamedDeviceName);
+ } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_SETTINGS)) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_settings_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ } else {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title, appLabel);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ }
} else {
mAlertParams.mTitle = getString(R.string.app_blocked_title);
mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8aed76f..004cb4c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5459,12 +5459,36 @@
<string name="app_streaming_blocked_title"><xliff:g id="activity" example="Permission dialog">%1$s</xliff:g> unavailable</string>
<!-- Title of the dialog shown when the permissioncontroller is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_permission_dialog">Permission needed</string>
+ <!-- Title of the dialog shown when the camera permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_camera_dialog">Camera unavailable</string>
+ <!-- Title of the dialog shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_fingerprint_dialog">Continue on phone</string>
+ <!-- Title of the dialog shown when the microphone permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_microphone_dialog">Microphone unavailable</string>
+ <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tv">Android TV settings unavailable</string>
+ <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet">Tablet settings unavailable</string>
+ <!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="default">Phone settings unavailable</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
+ <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
+ <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+ <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
+ <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+ <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string>
+ <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet">This app is requesting additional security. Try on your tablet instead.</string>
+ <!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default">This app is requesting additional security. Try on your phone instead.</string>
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b2dc82f..f177226 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3285,7 +3285,13 @@
<java-symbol type="string" name="app_streaming_blocked_title" />
<java-symbol type="string" name="app_streaming_blocked_title_for_permission_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_camera_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_fingerprint_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_microphone_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_settings_dialog" />
<java-symbol type="string" name="app_streaming_blocked_message" />
+ <java-symbol type="string" name="app_streaming_blocked_message_for_permission_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_message_for_fingerprint_dialog" />
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index da53ca5..b6635f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -665,6 +665,15 @@
mSurfaceControlTransactionFactory.getTransaction();
tx.setAlpha(mLeash, 0f);
tx.apply();
+
+ // When entering PiP this transaction will be applied within WindowContainerTransaction and
+ // ensure that the PiP has rounded corners.
+ final SurfaceControl.Transaction boundsChangeTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper
+ .crop(boundsChangeTx, mLeash, destinationBounds)
+ .round(boundsChangeTx, mLeash, true /* applyCornerRadius */);
+
mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
applyEnterPipSyncTransaction(destinationBounds, () -> {
mPipAnimationController
@@ -677,7 +686,7 @@
// mState is set right after the animation is kicked off to block any resize
// requests such as offsetPip that may have been called prior to the transition.
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
- }, null /* boundsChangeTransaction */);
+ }, boundsChangeTx);
}
private void onEndOfSwipePipToHomeTransition() {
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index a7ed091..4385a80 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,6 +1,14 @@
{
"presubmit": [
{
+ "name": "CtsCameraTestCases",
+ "options" : [
+ {
+ "include-filter": "android.hardware.camera2.cts.ImageReaderTest#testP010"
+ }
+ ]
+ },
+ {
"name": "GtsMediaTestCases",
"options" : [
{
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 31e1817..62c0d55 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -593,6 +593,12 @@
// (HAL_PIXEL_FORMAT_YCbCr_420_888) as HAL_PIXEL_FORMAT_YCbCr_420_888.
ALOGV("%s: Treat buffer format to 0x%x as HAL_PIXEL_FORMAT_YCbCr_420_888",
__FUNCTION__, bufferFormat);
+ } else if (imgReaderFmt == HAL_PIXEL_FORMAT_YCBCR_P010 &&
+ isPossibly10BitYUV(bufferFormat)) {
+ // Treat formats that are compatible with flexible 10-bit YUV
+ // (HAL_PIXEL_FORMAT_YCBCR_P010) as HAL_PIXEL_FORMAT_YCBCR_P010.
+ ALOGV("%s: Treat buffer format to 0x%x as HAL_PIXEL_FORMAT_YCBCR_P010",
+ __FUNCTION__, bufferFormat);
} else if (imgReaderFmt == HAL_PIXEL_FORMAT_BLOB &&
bufferFormat == HAL_PIXEL_FORMAT_RGBA_8888) {
// Using HAL_PIXEL_FORMAT_RGBA_8888 Gralloc buffers containing JPEGs to get around
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 39b560b..f4a39b3 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -17,7 +17,10 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "AndroidMediaUtils"
+#include <aidl/android/hardware/graphics/common/PlaneLayoutComponentType.h>
#include <hardware/camera3.h>
+#include <ui/GraphicBufferMapper.h>
+#include <ui/GraphicTypes.h>
#include <utils/Log.h>
#include "android_media_Utils.h"
@@ -81,6 +84,32 @@
}
}
+bool isPossibly10BitYUV(PixelFormat format) {
+ switch (static_cast<int>(format)) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGB_565:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_Y8:
+ case HAL_PIXEL_FORMAT_Y16:
+ case HAL_PIXEL_FORMAT_RAW16:
+ case HAL_PIXEL_FORMAT_RAW12:
+ case HAL_PIXEL_FORMAT_RAW10:
+ case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+ case HAL_PIXEL_FORMAT_BLOB:
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ case HAL_PIXEL_FORMAT_YV12:
+ case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ return false;
+
+ case HAL_PIXEL_FORMAT_YCBCR_P010:
+ default:
+ return true;
+ }
+}
+
uint32_t Image_getBlobSize(LockedImage* buffer, bool usingRGBAOverride) {
ALOGV("%s", __FUNCTION__);
LOG_ALWAYS_FATAL_IF(buffer == NULL, "Input buffer is NULL!!!");
@@ -279,6 +308,27 @@
return BAD_VALUE;
}
+ if (buffer->dataCb && buffer->dataCr) {
+ pData =
+ (idx == 0) ?
+ buffer->data :
+ (idx == 1) ?
+ buffer->dataCb :
+ buffer->dataCr;
+ // only map until last pixel
+ if (idx == 0) {
+ pStride = 2;
+ rStride = buffer->stride;
+ dataSize = buffer->stride * (buffer->height - 1) + buffer->width * 2;
+ } else {
+ pStride = buffer->chromaStep;
+ rStride = buffer->chromaStride;
+ dataSize = buffer->chromaStride * (buffer->height / 2 - 1) +
+ buffer->chromaStep * (buffer->width / 2);
+ }
+ break;
+ }
+
ySize = (buffer->stride * 2) * buffer->height;
cSize = ySize / 2;
pStride = (idx == 0) ? 2 : 4;
@@ -404,6 +454,7 @@
rStride = buffer->stride * 3;
break;
default:
+ ALOGV("%s: unrecognized format 0x%x", __FUNCTION__, fmt);
return BAD_VALUE;
}
@@ -415,6 +466,79 @@
return OK;
}
+static status_t extractP010Gralloc4PlaneLayout(
+ sp<GraphicBuffer> buffer, void *pData, int format, LockedImage *outputImage) {
+ using aidl::android::hardware::graphics::common::PlaneLayoutComponent;
+ using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
+
+ GraphicBufferMapper& mapper = GraphicBufferMapper::get();
+ std::vector<ui::PlaneLayout> planeLayouts;
+ status_t res = mapper.getPlaneLayouts(buffer->handle, &planeLayouts);
+ if (res != OK) {
+ return res;
+ }
+ constexpr int64_t Y_PLANE_COMPONENTS = int64_t(PlaneLayoutComponentType::Y);
+ constexpr int64_t CBCR_PLANE_COMPONENTS =
+ int64_t(PlaneLayoutComponentType::CB) | int64_t(PlaneLayoutComponentType::CR);
+ uint8_t *dataY = nullptr;
+ uint8_t *dataCb = nullptr;
+ uint8_t *dataCr = nullptr;
+ uint32_t strideY = 0;
+ uint32_t strideCbCr = 0;
+ for (const ui::PlaneLayout &layout : planeLayouts) {
+ ALOGV("gralloc4 plane: %s", layout.toString().c_str());
+ int64_t components = 0;
+ for (const PlaneLayoutComponent &component : layout.components) {
+ if (component.sizeInBits != 10) {
+ return BAD_VALUE;
+ }
+ components |= component.type.value;
+ }
+ if (components == Y_PLANE_COMPONENTS) {
+ if (layout.sampleIncrementInBits != 16) {
+ return BAD_VALUE;
+ }
+ if (layout.components[0].offsetInBits != 6) {
+ return BAD_VALUE;
+ }
+ dataY = (uint8_t *)pData + layout.offsetInBytes;
+ strideY = layout.strideInBytes;
+ } else if (components == CBCR_PLANE_COMPONENTS) {
+ if (layout.sampleIncrementInBits != 32) {
+ return BAD_VALUE;
+ }
+ for (const PlaneLayoutComponent &component : layout.components) {
+ if (component.type.value == int64_t(PlaneLayoutComponentType::CB)
+ && component.offsetInBits != 6) {
+ return BAD_VALUE;
+ }
+ if (component.type.value == int64_t(PlaneLayoutComponentType::CR)
+ && component.offsetInBits != 22) {
+ return BAD_VALUE;
+ }
+ }
+ dataCb = (uint8_t *)pData + layout.offsetInBytes;
+ dataCr = (uint8_t *)pData + layout.offsetInBytes + 2;
+ strideCbCr = layout.strideInBytes;
+ } else {
+ return BAD_VALUE;
+ }
+ }
+
+ outputImage->data = dataY;
+ outputImage->width = buffer->getWidth();
+ outputImage->height = buffer->getHeight();
+ outputImage->format = format;
+ outputImage->flexFormat = HAL_PIXEL_FORMAT_YCBCR_P010;
+ outputImage->stride = strideY;
+
+ outputImage->dataCb = dataCb;
+ outputImage->dataCr = dataCr;
+ outputImage->chromaStride = strideCbCr;
+ outputImage->chromaStep = 4;
+ return OK;
+}
+
status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage,
const Rect& rect, int fenceFd, LockedImage* outputImage) {
ALOGV("%s: Try to lock the GraphicBuffer", __FUNCTION__);
@@ -433,11 +557,12 @@
status_t res;
int format = buffer->getPixelFormat();
int flexFormat = format;
+
if (isPossiblyYUV(format)) {
res = buffer->lockAsyncYCbCr(inUsage, rect, &ycbcr, fenceFd);
if (res != OK) {
- ALOGW("lockAsyncYCbCr failed with error %d", res);
+ ALOGW("lockAsyncYCbCr failed with error %d (format = 0x%x)", res, format);
}
pData = ycbcr.y;
@@ -451,6 +576,11 @@
ALOGE("Lock buffer failed!");
return res;
}
+ if (isPossibly10BitYUV(format)
+ && OK == extractP010Gralloc4PlaneLayout(buffer, pData, format, outputImage)) {
+ ALOGV("%s: Successfully locked the P010 image", __FUNCTION__);
+ return OK;
+ }
}
outputImage->data = reinterpret_cast<uint8_t*>(pData);
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 12841c0..4feb4f51 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -35,6 +35,8 @@
bool isPossiblyYUV(PixelFormat format);
+bool isPossibly10BitYUV(PixelFormat format);
+
status_t getLockedImageInfo(LockedImage* buffer, int idx, int32_t containerFormat,
uint8_t **base, uint32_t *size, int *pixelStride, int *rowStride);
diff --git a/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata.xml
new file mode 100644
index 0000000..43a82fa
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_lte_mobiledata.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2018 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:width="42.3281dp"
+ android:height="24dp"
+ android:viewportWidth="42.3281"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.7871,12.959H11.8477V14.1016H10.7871V16H9.3633V14.1016H5.6777L5.6367,13.2344L9.3164,7.4688H10.7871V12.959ZM7.1309,12.959H9.3633V9.3965L9.2578,9.584L7.1309,12.959ZM19.5586,14.8926C19.25,15.2949 18.8223,15.5996 18.2754,15.8066C17.7285,16.0137 17.1074,16.1172 16.4121,16.1172C15.6973,16.1172 15.0645,15.9551 14.5137,15.6309C13.9629,15.3066 13.5371,14.8438 13.2363,14.2422C12.9395,13.6367 12.7852,12.9316 12.7734,12.127V11.459C12.7734,10.1699 13.082,9.1641 13.6992,8.4414C14.3164,7.7148 15.1777,7.3516 16.2832,7.3516C17.2324,7.3516 17.9863,7.5859 18.5449,8.0547C19.1035,8.5234 19.4395,9.1992 19.5527,10.082H18.0996C17.9355,9.0547 17.3398,8.541 16.3125,8.541C15.6484,8.541 15.1426,8.7813 14.7949,9.2617C14.4512,9.7383 14.2734,10.4395 14.2617,11.3652V12.0215C14.2617,12.9434 14.4551,13.6602 14.8418,14.1719C15.2324,14.6797 15.7734,14.9336 16.4648,14.9336C17.2227,14.9336 17.7617,14.7617 18.082,14.418V12.748H16.3242V11.623H19.5586V14.8926ZM25.6582,14.8164H29.5312V16H24.1758V7.4688H25.6582V14.8164ZM35.625,8.6641H32.9648V16H31.4941V8.6641H28.8574V7.4688H35.625V8.6641ZM41.7363,12.1914H38.2324V14.8164H42.3281V16H36.75V7.4688H42.2871V8.6641H38.2324V11.0195H41.7363V12.1914Z" />
+
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata.xml b/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata.xml
new file mode 100644
index 0000000..b80c47a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_4g_lte_plus_mobiledata.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2018 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:width="49.65dp"
+ android:height="24dp"
+ android:viewportWidth="49.65"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.7871,12.959H11.8477V14.1016H10.7871V16H9.3633V14.1016H5.6777L5.6367,13.2344L9.3164,7.4688H10.7871V12.959ZM7.1309,12.959H9.3633V9.3965L9.2578,9.584L7.1309,12.959ZM19.5586,14.8926C19.25,15.2949 18.8223,15.5996 18.2754,15.8066C17.7285,16.0137 17.1074,16.1172 16.4121,16.1172C15.6973,16.1172 15.0645,15.9551 14.5137,15.6309C13.9629,15.3066 13.5371,14.8438 13.2363,14.2422C12.9395,13.6367 12.7852,12.9316 12.7734,12.127V11.459C12.7734,10.1699 13.082,9.1641 13.6992,8.4414C14.3164,7.7148 15.1777,7.3516 16.2832,7.3516C17.2324,7.3516 17.9863,7.5859 18.5449,8.0547C19.1035,8.5234 19.4395,9.1992 19.5527,10.082H18.0996C17.9355,9.0547 17.3398,8.541 16.3125,8.541C15.6484,8.541 15.1426,8.7813 14.7949,9.2617C14.4512,9.7383 14.2734,10.4395 14.2617,11.3652V12.0215C14.2617,12.9434 14.4551,13.6602 14.8418,14.1719C15.2324,14.6797 15.7734,14.9336 16.4648,14.9336C17.2227,14.9336 17.7617,14.7617 18.082,14.418V12.748H16.3242V11.623H19.5586V14.8926ZM25.6582,14.8164H29.5312V16H24.1758V7.4688H25.6582V14.8164ZM35.625,8.6641H32.9648V16H31.4941V8.6641H28.8574V7.4688H35.625V8.6641ZM41.7363,12.1914H38.2324V14.8164H42.3281V16H36.75V7.4688H42.2871V8.6641H38.2324V11.0195H41.7363V12.1914Z" />
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M 47.3 9.74L 47.3 7.39L 46 7.39L 46 9.74L 43.65 9.74L 43.65 11.04L 46 11.04L 46 13.39L 47.3 13.39L 47.3 11.04L 49.65 11.04L 49.65 9.74Z" />
+
+</vector>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4162a67..df2685d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1504,6 +1504,12 @@
<!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] -->
<string name="data_connection_lte_plus">LTE+</string>
+ <!-- Content description of the data connection type 4G LTE . [CHAR LIMIT=NONE] -->
+ <string name="data_connection_4g_lte" translatable="false">4G LTE</string>
+
+ <!-- Content description of the data connection type 4G LTE+ . [CHAR LIMIT=NONE] -->
+ <string name="data_connection_4g_lte_plus" translatable="false">4G LTE+</string>
+
<!-- Content description of the data connection type 5Ge with HTML styling. DO NOT TRANSLATE [CHAR LIMIT=NONE] -->
<string name="data_connection_5ge_html" translate="false"> <i>5G <small>E</small></i> </string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2a28891..4ee2122 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1157,18 +1157,26 @@
}
/**
- * See {@link #getCarConnectionSummary(boolean)}
+ * See {@link #getCarConnectionSummary(boolean, boolean)}
*/
public String getCarConnectionSummary() {
- return getCarConnectionSummary(false);
+ return getCarConnectionSummary(false /* shortSummary */);
+ }
+
+ /**
+ * See {@link #getCarConnectionSummary(boolean, boolean)}
+ */
+ public String getCarConnectionSummary(boolean shortSummary) {
+ return getCarConnectionSummary(shortSummary, true /* useDisconnectedString */);
}
/**
* Returns android auto string that describes the connection state of this device.
*
* @param shortSummary {@code true} if need to return short version summary
+ * @param useDisconnectedString {@code true} if need to return disconnected summary string
*/
- public String getCarConnectionSummary(boolean shortSummary) {
+ public String getCarConnectionSummary(boolean shortSummary, boolean useDisconnectedString) {
boolean profileConnected = false; // at least one profile is connected
boolean a2dpNotConnected = false; // A2DP is preferred but not connected
boolean hfpNotConnected = false; // HFP is preferred but not connected
@@ -1286,9 +1294,10 @@
}
}
- return getBondState() == BluetoothDevice.BOND_BONDING ?
- mContext.getString(R.string.bluetooth_pairing) :
- mContext.getString(R.string.bluetooth_disconnected);
+ if (getBondState() == BluetoothDevice.BOND_BONDING) {
+ return mContext.getString(R.string.bluetooth_pairing);
+ }
+ return useDisconnectedString ? mContext.getString(R.string.bluetooth_disconnected) : null;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 5e91a14..c941954 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -160,6 +160,19 @@
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA),
TelephonyIcons.FOUR_G_PLUS);
}
+ } else if (config.show4glteForLte) {
+ networkToIconLookup.put(toIconKey(
+ TelephonyManager.NETWORK_TYPE_LTE),
+ TelephonyIcons.FOUR_G_LTE);
+ if (config.hideLtePlus) {
+ networkToIconLookup.put(toDisplayIconKey(
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA),
+ TelephonyIcons.FOUR_G_LTE);
+ } else {
+ networkToIconLookup.put(toDisplayIconKey(
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA),
+ TelephonyIcons.FOUR_G_LTE_PLUS);
+ }
} else {
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_LTE),
@@ -200,6 +213,7 @@
public boolean show4gFor3g = false;
public boolean alwaysShowCdmaRssi = false;
public boolean show4gForLte = false;
+ public boolean show4glteForLte = false;
public boolean hideLtePlus = false;
public boolean hspaDataDistinguishable;
public boolean alwaysShowDataRatIcon = false;
@@ -228,6 +242,8 @@
CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
config.show4gForLte = b.getBoolean(
CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
+ config.show4glteForLte = b.getBoolean(
+ CarrierConfigManager.KEY_SHOW_4GLTE_FOR_LTE_DATA_ICON_BOOL);
config.show4gFor3g = b.getBoolean(
CarrierConfigManager.KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL);
config.hideLtePlus = b.getBoolean(
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index d4e58f7..23e0923 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -39,6 +39,8 @@
public static final int ICON_3G = R.drawable.ic_3g_mobiledata;
public static final int ICON_4G = R.drawable.ic_4g_mobiledata;
public static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
+ public static final int ICON_4G_LTE = R.drawable.ic_4g_lte_mobiledata;
+ public static final int ICON_4G_LTE_PLUS = R.drawable.ic_4g_lte_plus_mobiledata;
public static final int ICON_5G_E = R.drawable.ic_5g_e_mobiledata;
public static final int ICON_1X = R.drawable.ic_1x_mobiledata;
public static final int ICON_5G = R.drawable.ic_5g_mobiledata;
@@ -225,6 +227,34 @@
TelephonyIcons.ICON_LTE_PLUS
);
+ public static final MobileIconGroup FOUR_G_LTE = new MobileIconGroup(
+ "4G LTE",
+ null,
+ null,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0,
+ 0,
+ 0,
+ 0,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.data_connection_4g_lte,
+ TelephonyIcons.ICON_4G_LTE
+ );
+
+ public static final MobileIconGroup FOUR_G_LTE_PLUS = new MobileIconGroup(
+ "4G LTE+",
+ null,
+ null,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0,
+ 0,
+ 0,
+ 0,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.data_connection_4g_lte_plus,
+ TelephonyIcons.ICON_4G_LTE_PLUS
+ );
+
public static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
"5Ge",
null,
@@ -327,6 +357,8 @@
ICON_NAME_TO_ICON.put("h+", H_PLUS);
ICON_NAME_TO_ICON.put("4g", FOUR_G);
ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS);
+ ICON_NAME_TO_ICON.put("4glte", FOUR_G_LTE);
+ ICON_NAME_TO_ICON.put("4glte+", FOUR_G_LTE_PLUS);
ICON_NAME_TO_ICON.put("5ge", LTE_CA_5G_E);
ICON_NAME_TO_ICON.put("lte", LTE);
ICON_NAME_TO_ICON.put("lte+", LTE_PLUS);
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 7cab0c9..a7e6102 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -73,6 +73,9 @@
android:layout_height="@dimen/qs_footer_icon_size"
android:layout_gravity="center"
android:background="@android:color/transparent"
+ android:focusable="false"
+ android:clickable="false"
+ android:importantForAccessibility="yes"
android:contentDescription="@string/accessibility_quick_settings_settings"
android:scaleType="centerInside"
android:src="@drawable/ic_settings"
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 649bfce..43d91a2 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -200,49 +200,45 @@
}
};
- private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener =
+ @VisibleForTesting
+ PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener =
new PrivacyDotViewController.ShowingListener() {
@Override
public void onPrivacyDotShown(@Nullable View v) {
- // We don't need to control the window visibility when the hwc doesn't support screen
- // decoration since the overlay windows are always visible in this case.
- if (mHwcScreenDecorationSupport == null || v == null) {
- return;
- }
- mExecutor.execute(() -> {
- for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- if (mOverlays[i] == null) {
- continue;
- }
- final ViewGroup overlayView = mOverlays[i].getRootView();
- if (overlayView.findViewById(v.getId()) != null) {
- overlayView.setVisibility(View.VISIBLE);
- }
- }
- });
+ setOverlayWindowVisibilityIfViewExist(v, View.VISIBLE);
}
@Override
public void onPrivacyDotHidden(@Nullable View v) {
- // We don't need to control the window visibility when the hwc doesn't support screen
- // decoration since the overlay windows are always visible in this case.
- if (mHwcScreenDecorationSupport == null || v == null) {
- return;
- }
- mExecutor.execute(() -> {
- for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- if (mOverlays[i] == null) {
- continue;
- }
- final ViewGroup overlayView = mOverlays[i].getRootView();
- if (overlayView.findViewById(v.getId()) != null) {
- overlayView.setVisibility(View.INVISIBLE);
- }
- }
- });
+ setOverlayWindowVisibilityIfViewExist(v, View.INVISIBLE);
}
};
+ @VisibleForTesting
+ protected void setOverlayWindowVisibilityIfViewExist(@Nullable View view,
+ @View.Visibility int visibility) {
+ if (view == null) {
+ return;
+ }
+ mExecutor.execute(() -> {
+ // We don't need to control the window visibility if rounded corners or cutout is drawn
+ // on sw layer since the overlay windows are always visible in this case.
+ if (mOverlays == null || !isOnlyPrivacyDotInSwLayer()) {
+ return;
+ }
+
+ for (final OverlayWindow overlay : mOverlays) {
+ if (overlay == null) {
+ continue;
+ }
+ if (overlay.getView(view.getId()) != null) {
+ overlay.getRootView().setVisibility(visibility);
+ return;
+ }
+ }
+ });
+ }
+
private static boolean eq(DisplayDecorationSupport a, DisplayDecorationSupport b) {
if (a == null) return (b == null);
if (b == null) return false;
@@ -268,7 +264,6 @@
mDotViewController = dotViewController;
mThreadFactory = threadFactory;
mDotFactory = dotFactory;
- dotViewController.setShowingListener(mPrivacyDotShowingListener);
}
@Override
@@ -425,18 +420,24 @@
removeHwcOverlay();
}
final DisplayCutout cutout = getCutout();
+ final boolean isOnlyPrivacyDotInSwLayer = isOnlyPrivacyDotInSwLayer();
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
- || shouldShowPrivacyDot(i, cutout)) {
+ if (shouldShowSwLayerCutout(i, cutout) || shouldShowSwLayerRoundedCorner(i, cutout)
+ || shouldShowSwLayerPrivacyDot(i, cutout)) {
Pair<List<DecorProvider>, List<DecorProvider>> pair =
DecorProviderKt.partitionAlignedBound(decorProviders, i);
decorProviders = pair.getSecond();
- createOverlay(i, cutout, pair.getFirst());
+ createOverlay(i, cutout, pair.getFirst(), isOnlyPrivacyDotInSwLayer);
} else {
removeOverlay(i);
}
}
+ if (isOnlyPrivacyDotInSwLayer) {
+ mDotViewController.setShowingListener(mPrivacyDotShowingListener);
+ } else {
+ mDotViewController.setShowingListener(null);
+ }
final View tl, tr, bl, br;
if ((tl = getOverlayView(R.id.privacy_dot_top_left_container)) != null
&& (tr = getOverlayView(R.id.privacy_dot_top_right_container)) != null
@@ -530,19 +531,51 @@
mOverlays[pos] = null;
}
+ @View.Visibility
+ private int getWindowVisibility(@NonNull OverlayWindow overlay,
+ boolean isOnlyPrivacyDotInSwLayer) {
+ if (!isOnlyPrivacyDotInSwLayer) {
+ // Multiple views inside overlay, no need to optimize
+ return View.VISIBLE;
+ }
+
+ int[] ids = {
+ R.id.privacy_dot_top_left_container,
+ R.id.privacy_dot_top_right_container,
+ R.id.privacy_dot_bottom_left_container,
+ R.id.privacy_dot_bottom_right_container
+ };
+ for (int id: ids) {
+ final View view = overlay.getView(id);
+ if (view != null && view.getVisibility() == View.VISIBLE) {
+ // Only privacy dot in sw layers, overlay shall be VISIBLE if one of privacy dot
+ // views inside this overlay is VISIBLE
+ return View.VISIBLE;
+ }
+ }
+ // Only privacy dot in sw layers, overlay shall be INVISIBLE like default if no privacy dot
+ // view inside this overlay is VISIBLE.
+ return View.INVISIBLE;
+ }
+
private void createOverlay(
@BoundsPosition int pos,
@Nullable DisplayCutout cutout,
- @NonNull List<DecorProvider> decorProviders) {
+ @NonNull List<DecorProvider> decorProviders,
+ boolean isOnlyPrivacyDotInSwLayer) {
if (mOverlays == null) {
mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
}
if (mOverlays[pos] != null) {
+ // When mOverlay[pos] is not null and only privacy dot in sw layer, use privacy dot
+ // view's visibility
+ mOverlays[pos].getRootView().setVisibility(
+ getWindowVisibility(mOverlays[pos], isOnlyPrivacyDotInSwLayer));
return;
}
- mOverlays[pos] = overlayForPosition(pos, decorProviders);
+ mOverlays[pos] = overlayForPosition(pos, decorProviders, isOnlyPrivacyDotInSwLayer);
final ViewGroup overlayView = mOverlays[pos].getRootView();
overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
overlayView.setAlpha(0);
@@ -612,18 +645,18 @@
*/
private OverlayWindow overlayForPosition(
@BoundsPosition int pos,
- @NonNull List<DecorProvider> decorProviders) {
+ @NonNull List<DecorProvider> decorProviders,
+ boolean isOnlyPrivacyDotInSwLayer) {
final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos);
decorProviders.forEach(provider -> {
removeOverlayView(provider.getViewId());
currentOverlay.addDecorProvider(provider, mRotation);
- // If the hwc supports screen decoration and privacy dot is enabled, it means there will
- // be only privacy dot in mOverlay. So set the initial visibility of mOverlays to
- // INVISIBLE and will only set it to VISIBLE when the privacy dot is showing.
- if (mHwcScreenDecorationSupport != null) {
- currentOverlay.getRootView().setVisibility(View.INVISIBLE);
- }
});
+ // When only privacy dot in mOverlay, set the initial visibility of mOverlays to
+ // INVISIBLE and set it to VISIBLE when the privacy dot is showing.
+ if (isOnlyPrivacyDotInSwLayer) {
+ currentOverlay.getRootView().setVisibility(View.INVISIBLE);
+ }
return currentOverlay;
}
@@ -842,6 +875,7 @@
pw.println("ScreenDecorations state:");
pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
+ pw.println(" isOnlyPrivacyDotInSwLayer:" + isOnlyPrivacyDotInSwLayer());
pw.println(" mPendingRotationChange:" + mPendingRotationChange);
if (mHwcScreenDecorationSupport != null) {
pw.println(" mHwcScreenDecorationSupport:");
@@ -923,7 +957,7 @@
return;
}
rounded.setVisibility(View.GONE);
- if (shouldShowRoundedCorner(pos, cutout)) {
+ if (shouldShowSwLayerRoundedCorner(pos, cutout)) {
final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
setRoundedCornerOrientation(rounded, gravity);
@@ -997,23 +1031,32 @@
}
}
- private boolean shouldShowRoundedCorner(@BoundsPosition int pos,
+ private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos,
@Nullable DisplayCutout cutout) {
return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout)
&& mHwcScreenDecorationSupport == null;
}
- private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout);
}
- private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ private boolean shouldShowSwLayerCutout(@BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
return (bounds != null && !bounds[rotatedPos].isEmpty()
&& mHwcScreenDecorationSupport == null);
}
+ private boolean isOnlyPrivacyDotInSwLayer() {
+ return isPrivacyDotEnabled()
+ && (mHwcScreenDecorationSupport != null
+ || (!hasRoundedCorners() && !shouldDrawCutout())
+ );
+ }
+
private boolean shouldDrawCutout() {
return shouldDrawCutout(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 990f04b..c147fde 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -20,6 +20,8 @@
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.Region;
@@ -148,7 +150,10 @@
@VisibleForTesting
public enum DreamEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The screensaver has been swiped up.")
- DREAM_SWIPED(988);
+ DREAM_SWIPED(988),
+
+ @UiEvent(doc = "The bouncer has become fully visible over dream.")
+ DREAM_BOUNCER_FULLY_VISIBLE(1056);
private final int mId;
@@ -278,6 +283,15 @@
animation -> {
setPanelExpansion((float) animation.getAnimatedValue());
});
+ if (!mBouncerInitiallyShowing && targetExpansion == KeyguardBouncer.EXPANSION_VISIBLE) {
+ animator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
+ }
+ });
+ }
return animator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index aac28d1..547d13c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -611,12 +611,11 @@
private void bindActionButtons(MediaData data) {
MediaButton semanticActions = data.getSemanticActions();
- ImageButton[] genericButtons = new ImageButton[]{
- mMediaViewHolder.getAction0(),
- mMediaViewHolder.getAction1(),
- mMediaViewHolder.getAction2(),
- mMediaViewHolder.getAction3(),
- mMediaViewHolder.getAction4()};
+
+ List<ImageButton> genericButtons = new ArrayList<>();
+ for (int id : MediaViewHolder.Companion.getGenericButtonIds()) {
+ genericButtons.add(mMediaViewHolder.getAction(id));
+ }
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
@@ -630,7 +629,7 @@
for (int id : SEMANTIC_ACTIONS_ALL) {
ImageButton button = mMediaViewHolder.getAction(id);
MediaAction action = semanticActions.getActionById(id);
- setSemanticButton(button, action);
+ setSemanticButton(button, action, semanticActions);
}
} else {
// Hide buttons that only appear for semantic actions
@@ -643,19 +642,19 @@
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
List<MediaAction> actions = data.getActions();
int i = 0;
- for (; i < actions.size(); i++) {
+ for (; i < actions.size() && i < genericButtons.size(); i++) {
boolean showInCompact = actionsWhenCollapsed.contains(i);
setGenericButton(
- genericButtons[i],
+ genericButtons.get(i),
actions.get(i),
collapsedSet,
expandedSet,
showInCompact);
}
- for (; i < 5; i++) {
+ for (; i < genericButtons.size(); i++) {
// Hide any unused buttons
setGenericButton(
- genericButtons[i],
+ genericButtons.get(i),
/* mediaAction= */ null,
collapsedSet,
expandedSet,
@@ -699,7 +698,10 @@
setVisibleAndAlpha(collapsedSet, button.getId(), visible && showInCompact);
}
- private void setSemanticButton(final ImageButton button, @Nullable MediaAction mediaAction) {
+ private void setSemanticButton(
+ final ImageButton button,
+ @Nullable MediaAction mediaAction,
+ MediaButton semanticActions) {
AnimationBindHandler animHandler;
if (button.getTag() == null) {
animHandler = new AnimationBindHandler();
@@ -710,7 +712,7 @@
animHandler.tryExecute(() -> {
bindButtonWithAnimations(button, mediaAction, animHandler);
- setSemanticButtonVisibleAndAlpha(button.getId(), mediaAction);
+ setSemanticButtonVisibleAndAlpha(button.getId(), mediaAction, semanticActions);
});
}
@@ -773,12 +775,14 @@
private void setSemanticButtonVisibleAndAlpha(
int buttonId,
- MediaAction mediaAction) {
+ @Nullable MediaAction mediaAction,
+ MediaButton semanticActions) {
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(buttonId);
boolean hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId);
- boolean shouldBeHiddenDueToScrubbing = hideWhenScrubbing && mIsScrubbing;
+ boolean shouldBeHiddenDueToScrubbing =
+ scrubbingTimeViewsEnabled(semanticActions) && hideWhenScrubbing && mIsScrubbing;
boolean visible = mediaAction != null && !shouldBeHiddenDueToScrubbing;
setVisibleAndAlpha(expandedSet, buttonId, visible);
@@ -791,7 +795,8 @@
// Update visibilities of the scrubbing time views and the scrubbing-dependent buttons.
bindScrubbingTime(mMediaData);
SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach((id) ->
- setSemanticButtonVisibleAndAlpha(id, semanticActions.getActionById(id)));
+ setSemanticButtonVisibleAndAlpha(
+ id, semanticActions.getActionById(id), semanticActions));
// Trigger a state refresh so that we immediately update visibilities.
mMediaViewController.refreshState();
}
@@ -802,7 +807,7 @@
int elapsedTimeId = mMediaViewHolder.getScrubbingElapsedTimeView().getId();
int totalTimeId = mMediaViewHolder.getScrubbingTotalTimeView().getId();
- boolean visible = data.getSemanticActions() != null && mIsScrubbing;
+ boolean visible = scrubbingTimeViewsEnabled(data.getSemanticActions()) && mIsScrubbing;
setVisibleAndAlpha(expandedSet, elapsedTimeId, visible);
setVisibleAndAlpha(expandedSet, totalTimeId, visible);
// Never show in collapsed
@@ -810,6 +815,14 @@
setVisibleAndAlpha(collapsedSet, totalTimeId, false);
}
+ private boolean scrubbingTimeViewsEnabled(@Nullable MediaButton semanticActions) {
+ // The scrubbing time views replace the SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING action views,
+ // so we should only allow scrubbing times to be shown if those action views are present.
+ return semanticActions != null && SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.stream().allMatch(
+ id -> semanticActions.getActionById(id) != 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.
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index ad93855..9a2977d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -161,6 +161,10 @@
@JvmField
val MAX_COMPACT_ACTIONS = 3
+ // Maximum number of actions allowed in expanded view
+ @JvmField
+ val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size
+
/** Maximum number of [PlaybackState.CustomAction] buttons supported */
@JvmField
val MAX_CUSTOM_ACTIONS = 4
@@ -727,6 +731,11 @@
if (actions != null) {
for ((index, action) in actions.withIndex()) {
+ if (index == MAX_NOTIFICATION_ACTIONS) {
+ Log.w(TAG, "Too many notification actions for ${sbn.key}," +
+ " limiting to first $MAX_NOTIFICATION_ACTIONS")
+ break
+ }
if (action.getIcon() == null) {
if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
actionsToShowCollapsed.remove(index)
@@ -843,18 +852,23 @@
}
/**
- * Get a [MediaAction] representing one of
- * - [PlaybackState.ACTION_PLAY]
- * - [PlaybackState.ACTION_PAUSE]
- * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
- * - [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * Create a [MediaAction] for a given action and media session
+ *
+ * @param controller MediaController for the session
+ * @param stateActions The actions included with the session's [PlaybackState]
+ * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ * [PlaybackState.ACTION_PLAY]
+ * [PlaybackState.ACTION_PAUSE]
+ * [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
+ * [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * @return A [MediaAction] with correct values set, or null if the state doesn't support it
*/
private fun getStandardAction(
controller: MediaController,
stateActions: Long,
- action: Long
+ @PlaybackState.Actions action: Long
): MediaAction? {
- if (stateActions and action == 0L) {
+ if (!includesAction(stateActions, action)) {
return null
}
@@ -896,6 +910,17 @@
}
/**
+ * Check whether the actions from a [PlaybackState] include a specific action
+ */
+ private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
+ if ((action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+ (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)) {
+ return true
+ }
+ return (stateActions and action != 0L)
+ }
+
+ /**
* Get a [MediaAction] representing a [PlaybackState.CustomAction]
*/
private fun getCustomAction(
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
index 34a77f2..8964d71 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
@@ -178,5 +178,14 @@
R.id.dismiss,
R.id.settings
)
+
+ // Buttons used for notification-based actions
+ val genericButtonIds = setOf(
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4
+ )
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index b4f8c10..58e07db 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -32,9 +32,11 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -83,7 +85,11 @@
super(view, R.drawable.nav_background);
mView = view;
mLightTransitionsController = new LightBarTransitionsController(
- view.getContext(), this, commandQueue);
+ view.getContext(),
+ this,
+ commandQueue,
+ Dependency.get(KeyguardStateController.class),
+ Dependency.get(StatusBarStateController.class));
mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
.getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
mDarkIntensityListeners = new ArrayList();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 35aea5c..cdc6b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -55,8 +55,8 @@
import androidx.annotation.NonNull;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
@@ -78,9 +78,9 @@
import java.util.function.Consumer;
import javax.inject.Inject;
-import javax.inject.Singleton;
-@Singleton
+/** */
+@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
ComponentCallbacks, Dumpable {
@@ -88,6 +88,7 @@
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
private final NavigationBarOverlayController mNavBarOverlayController;
+ private final LightBarTransitionsController.Factory mLightBarTransitionsControllerFactory;
private boolean mInitialized;
private CommandQueue mCommandQueue;
private OverviewProxyService mOverviewProxyService;
@@ -154,10 +155,15 @@
private BackAnimation mBackAnimation;
@Inject
- public TaskbarDelegate(Context context) {
- mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
- .create(context);
- mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
+ public TaskbarDelegate(
+ Context context,
+ EdgeBackGestureHandler.Factory edgeBackGestureHandlerFactory,
+ NavigationBarOverlayController navigationBarOverlayController,
+ LightBarTransitionsController.Factory lightBarTransitionsControllerFactory
+ ) {
+ mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
+ mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
+ mNavBarOverlayController = navigationBarOverlayController;
if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
mNavBarOverlayController.init(mNavbarOverlayVisibilityChangeCallback,
mEdgeBackGestureHandler::updateNavigationBarOverlayExcludeRegion);
@@ -185,14 +191,15 @@
dumpManager.registerDumpable(this);
mAutoHideController = autoHideController;
mLightBarController = lightBarController;
- mLightBarTransitionsController = createLightBarTransitionsController();
mPipOptional = pipOptional;
mBackAnimation = backAnimation;
+ mLightBarTransitionsController = createLightBarTransitionsController();
}
// Separated into a method to keep setDependencies() clean/readable.
private LightBarTransitionsController createLightBarTransitionsController() {
- return new LightBarTransitionsController(mContext,
+
+ LightBarTransitionsController controller = mLightBarTransitionsControllerFactory.create(
new LightBarTransitionsController.DarkIntensityApplier() {
@Override
public void applyDarkIntensity(float darkIntensity) {
@@ -203,13 +210,10 @@
public int getTintAnimationDuration() {
return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
}
- }, mCommandQueue) {
- @Override
- public boolean supportsIconTintForNavMode(int navigationMode) {
- // Always tint taskbar nav buttons (region sampling handles gesture bar separately).
- return true;
- }
- };
+ });
+ controller.overrideIconTintForNavMode(true);
+
+ return controller;
}
public void init(int displayId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 3f394e7..d701f33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -38,7 +38,6 @@
import com.android.systemui.qs.dagger.QSScope
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.phone.SettingsButton
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
@@ -89,8 +88,7 @@
updateVisibility()
}
- private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
- private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+ private val settingsButtonContainer: View = view.findViewById(R.id.settings_button_container)
private val securityFootersContainer: ViewGroup? =
view.findViewById(R.id.security_footers_container)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
@@ -121,7 +119,7 @@
if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return@OnClickListener
}
- if (v === settingsButton) {
+ if (v === settingsButtonContainer) {
if (!deviceProvisionedController.isCurrentUserSetup) {
// If user isn't setup just unlock the device and dump them back at SUW.
activityStarter.postQSRunnableDismissingKeyguard {}
@@ -166,7 +164,7 @@
} else {
powerMenuLite.visibility = View.GONE
}
- settingsButton.setOnClickListener(onClickListener)
+ settingsButtonContainer.setOnClickListener(onClickListener)
multiUserSetting.isListening = true
val securityFooter = securityFooterController.view
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 9413a90..3417d49 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -29,7 +29,6 @@
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
import com.android.systemui.statusbar.phone.MultiUserSwitch
-import com.android.systemui.statusbar.phone.SettingsButton
/**
* Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
@@ -38,7 +37,6 @@
*/
class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private lateinit var settingsContainer: View
- private lateinit var settingsButton: SettingsButton
private lateinit var multiUserSwitch: MultiUserSwitch
private lateinit var multiUserAvatar: ImageView
@@ -47,15 +45,14 @@
override fun onFinishInflate() {
super.onFinishInflate()
- settingsButton = findViewById(R.id.settings_button)
settingsContainer = findViewById(R.id.settings_button_container)
multiUserSwitch = findViewById(R.id.multi_user_switch)
multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
- if (settingsButton.background is RippleDrawable) {
- (settingsButton.background as RippleDrawable).setForceSoftware(true)
+ if (settingsContainer.background is RippleDrawable) {
+ (settingsContainer.background as RippleDrawable).setForceSoftware(true)
}
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
}
@@ -82,7 +79,7 @@
private fun updateClickabilities() {
multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
- settingsButton.isClickable = settingsButton.visibility == VISIBLE
+ settingsContainer.isClickable = settingsContainer.visibility == VISIBLE
}
private fun updateVisibilities(
@@ -91,7 +88,7 @@
settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE
val isDemo = UserManager.isDeviceInDemoMode(context)
- settingsButton.visibility = if (isDemo) INVISIBLE else VISIBLE
+ settingsContainer.visibility = if (isDemo) INVISIBLE else VISIBLE
}
fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 1401423..4f27fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -134,7 +134,7 @@
uiExecutor = e
}
- fun setShowingListener(l: ShowingListener) {
+ fun setShowingListener(l: ShowingListener?) {
showingListener = l
}
@@ -573,7 +573,7 @@
}
}
- public interface ShowingListener {
+ interface ShowingListener {
fun onPrivacyDotShown(v: View?)
fun onPrivacyDotHidden(v: View?)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index d68f371..7fb115d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -52,9 +52,9 @@
private var maxKeyguardNotifications by notNull<Int>()
/**
- * Minimum space between two notifications. There might be more space, see [calculateGapHeight].
+ * Minimum space between two notifications, see [calculateGapAndDividerHeight].
*/
- private var notificationPadding by notNull<Int>()
+ private var dividerHeight by notNull<Int>()
init {
updateResources()
@@ -84,16 +84,20 @@
val onLockscreen = true
val showableRows = children.filter { it.isShowable(onLockscreen) }
val showableRowsCount = showableRows.count()
+ log { "\tshowableRowsCount=$showableRowsCount "}
+
showableRows.forEachIndexed { i, current ->
val spaceNeeded = current.spaceNeeded(count, previous, stack, onLockscreen)
+ val spaceAfter = remainingSpace - spaceNeeded
previous = current
- log { "\ti=$i spaceNeeded=$spaceNeeded remainingSpace=$remainingSpace" }
+ log { "\ti=$i spaceNeeded=$spaceNeeded remainingSpace=$remainingSpace " +
+ "spaceAfter=$spaceAfter" }
if (remainingSpace - spaceNeeded >= 0 && count < maxKeyguardNotifications) {
count += 1
remainingSpace -= spaceNeeded
} else if (remainingSpace - spaceNeeded > -shelfHeight && i == showableRowsCount - 1) {
- log { "Showing all notifications. Shelf is not be needed." }
+ log { "Show all notifications. Shelf not needed." }
// If this is the last one, and it fits using the space shelf would use, then we can
// display it, as the shelf will not be needed (as all notifications are shown).
return count + 1
@@ -139,8 +143,7 @@
height += spaceNeeded
count += 1
} else {
- val gapBeforeFirstViewInShelf = current.calculateGapHeight(stack, previous, count)
- height += gapBeforeFirstViewInShelf
+ height += current.calculateGapAndDividerHeight(stack, previous, count)
height += shelfHeight
log { "returning height with shelf -> $height" }
return height
@@ -155,7 +158,7 @@
maxKeyguardNotifications =
infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
- notificationPadding =
+ dividerHeight =
max(1, resources.getDimensionPixelSize(R.dimen.notification_divider_height))
}
@@ -177,12 +180,7 @@
} else {
intrinsicHeight.toFloat()
}
- if (visibleIndex != 0) {
- size += notificationPadding
- }
- val gapHeight = calculateGapHeight(stack, previousView, visibleIndex)
- log { "\ti=$visibleIndex gapHeight=$gapHeight"}
- size += gapHeight
+ size += calculateGapAndDividerHeight(stack, previousView, visibleIndex)
return size
}
@@ -202,11 +200,17 @@
return true
}
- private fun ExpandableView.calculateGapHeight(
+ private fun ExpandableView.calculateGapAndDividerHeight(
stack: NotificationStackScrollLayout,
previous: ExpandableView?,
visibleIndex: Int
- ) = stack.calculateGapHeight(previous, /* current= */ this, visibleIndex)
+ ) : Float {
+ var height = stack.calculateGapHeight(previous, /* current= */ this, visibleIndex)
+ if (visibleIndex != 0) {
+ height += dividerHeight
+ }
+ return height
+ }
/**
* Can a view be shown on the lockscreen when calculating the number of allowed notifications to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 7ceeb46..c77b0d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -1707,7 +1707,7 @@
@Nullable ActivityLaunchAnimator.Controller animationController,
boolean showOverLockscreenWhenLocked) {
startActivity(intent, dismissShade, animationController, showOverLockscreenWhenLocked,
- UserHandle.CURRENT);
+ getActivityUserHandle(intent));
}
@Override
@@ -1794,7 +1794,7 @@
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
startActivityDismissingKeyguard(intent, false, dismissShade,
false /* disallowEnterPictureInPictureWhileLaunching */, callback, 0,
- null /* animationController */, UserHandle.CURRENT);
+ null /* animationController */, getActivityUserHandle(intent));
}
public void setQsExpanded(boolean expanded) {
@@ -2424,7 +2424,7 @@
boolean dismissShade, int flags) {
startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade,
false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */,
- flags, null /* animationController */, UserHandle.CURRENT);
+ flags, null /* animationController */, getActivityUserHandle(intent));
}
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
@@ -2867,7 +2867,7 @@
null /* callback */,
0 /* flags */,
animationController,
- UserHandle.CURRENT),
+ getActivityUserHandle(intent)),
delay);
}
@@ -3601,7 +3601,8 @@
mLaunchEmergencyActionWhenFinishedWaking = false;
Intent emergencyIntent = getEmergencyActionIntent();
if (emergencyIntent != null) {
- mContext.startActivityAsUser(emergencyIntent, UserHandle.CURRENT);
+ mContext.startActivityAsUser(emergencyIntent,
+ getActivityUserHandle(emergencyIntent));
}
}
updateScrimController();
@@ -4499,4 +4500,20 @@
@Override
public void dispatchDemoCommand(String command, Bundle args) { }
};
+
+ /**
+ * Determines what UserHandle to use when launching an activity.
+ *
+ * We want to ensure that activities that are launched within the systemui process should be
+ * launched as user of the current process.
+ * @param intent
+ * @return UserHandle
+ */
+ private UserHandle getActivityUserHandle(Intent intent) {
+ if (intent.getComponent() != null
+ && mContext.getPackageName().equals(intent.getComponent().getPackageName())) {
+ return new UserHandle(UserHandle.myUserId());
+ }
+ return UserHandle.CURRENT;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 7aa87e3..2677c3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -26,7 +26,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.CommandQueue;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -53,12 +52,12 @@
@Inject
public DarkIconDispatcherImpl(
Context context,
- CommandQueue commandQueue,
+ LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
DumpManager dumpManager) {
mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
- mTransitionsController = new LightBarTransitionsController(context, this, commandQueue);
+ mTransitionsController = lightBarTransitionsControllerFactory.create(this);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index d11e330..b6ad9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,7 +24,6 @@
import android.util.MathUtils;
import android.util.TimeUtils;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -35,6 +34,10 @@
import java.io.PrintWriter;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
/**
* Class to control all aspects about light bar changes.
*/
@@ -69,13 +72,19 @@
};
private final Context mContext;
+ private Boolean mOverrideIconTintForNavMode;
- public LightBarTransitionsController(Context context, DarkIntensityApplier applier,
- CommandQueue commandQueue) {
+ @AssistedInject
+ public LightBarTransitionsController(
+ Context context,
+ @Assisted DarkIntensityApplier applier,
+ CommandQueue commandQueue,
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController) {
mApplier = applier;
mHandler = new Handler();
- mKeyguardStateController = Dependency.get(KeyguardStateController.class);
- mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
mCommandQueue = commandQueue;
mCommandQueue.addCallback(this);
mStatusBarStateController.addCallback(this);
@@ -230,11 +239,19 @@
}
/**
+ * Specify an override value to return for {@link #overrideIconTintForNavMode(boolean)}.
+ */
+ public void overrideIconTintForNavMode(boolean overrideValue) {
+ mOverrideIconTintForNavMode = overrideValue;
+ }
+ /**
* Return whether to use the tint calculated in this class for nav icons.
*/
public boolean supportsIconTintForNavMode(int navigationMode) {
// In gesture mode, we already do region sampling to update tint based on content beneath.
- return !QuickStepContract.isGesturalMode(navigationMode);
+ return mOverrideIconTintForNavMode != null
+ ? mOverrideIconTintForNavMode
+ : !QuickStepContract.isGesturalMode(navigationMode);
}
/**
@@ -244,4 +261,11 @@
void applyDarkIntensity(float darkIntensity);
int getTintAnimationDuration();
}
+
+ /** Injectable factory for construction a LightBarTransitionsController. */
+ @AssistedFactory
+ public interface Factory {
+ /** */
+ LightBarTransitionsController create(DarkIntensityApplier darkIntensityApplier);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index bf54677..5e5317d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -24,13 +24,15 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.widget.Button;
-import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
-public class SettingsButton extends AlphaOptimizedImageButton {
+public class SettingsButton extends AlphaOptimizedImageView {
private static final boolean TUNER_ENABLE_AVAILABLE = false;
@@ -170,6 +172,12 @@
mAnimator.start();
}
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ info.setClassName(Button.class.getName());
+ }
+
private final Runnable mLongPressCallback = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
index ad8dc82..9cfebdd 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.tv.VpnStatusObserver
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel
+import com.android.systemui.theme.ThemeOverlayController
import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
@@ -108,6 +109,12 @@
@ClassKey(StorageNotification::class)
abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
+ /** Inject into ThemeOverlayController. */
+ @Binds
+ @IntoMap
+ @ClassKey(ThemeOverlayController::class)
+ abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
+
/** Inject into ToastUI. */
@Binds
@IntoMap
@@ -161,4 +168,4 @@
@IntoMap
@ClassKey(WMShell::class)
abstract fun bindWMShell(sysui: WMShell): CoreStartable
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index bcccbc7..50bd9b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
@@ -127,6 +128,7 @@
private CornerDecorProvider mPrivacyDotBottomRightDecorProvider;
@Mock
private Display.Mode mDisplayMode;
+ private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
@Before
public void setup() {
@@ -195,10 +197,24 @@
super.onTuningChanged(key, newValue);
mExecutor.runAllReady();
}
+
+ @Override
+ protected void setOverlayWindowVisibilityIfViewExist(@Nullable View view,
+ @View.Visibility int visibility) {
+ super.setOverlayWindowVisibilityIfViewExist(view, visibility);
+ mExecutor.runAllReady();
+ }
});
reset(mTunerService);
- }
+ try {
+ mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass()
+ .getDeclaredConstructor(ScreenDecorations.class)
+ .newInstance(mScreenDecorations);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
private void verifyRoundedCornerViewsVisibility(
@DisplayCutout.BoundsPosition final int overlayId,
@@ -277,43 +293,59 @@
verifyBottomDotViewsVisibility(visibility);
}
- private void verifyOverlaysExistAndAdded(final boolean left, final boolean top,
- final boolean right, final boolean bottom) {
+ private void verifyOverlaysExistAndAdded(boolean left, boolean top, boolean right,
+ boolean bottom, @Nullable Integer visibilityIfExist) {
if (left || top || right || bottom) {
assertNotNull(mScreenDecorations.mOverlays);
} else {
- verify(mWindowManager, never()).addView(any(), any());
+ assertNull(mScreenDecorations.mOverlays);
return;
}
if (left) {
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+ final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT];
+ assertNotNull(overlay);
verify(mWindowManager, times(1)).addView(
- eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()), any());
+ eq(overlay.getRootView()), any());
+ if (visibilityIfExist != null) {
+ assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
+ }
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
}
if (top) {
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP];
+ assertNotNull(overlay);
verify(mWindowManager, times(1)).addView(
- eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()), any());
+ eq(overlay.getRootView()), any());
+ if (visibilityIfExist != null) {
+ assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
+ }
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
}
if (right) {
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT];
+ assertNotNull(overlay);
verify(mWindowManager, times(1)).addView(
- eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()), any());
+ eq(overlay.getRootView()), any());
+ if (visibilityIfExist != null) {
+ assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
+ }
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
}
if (bottom) {
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ final OverlayWindow overlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM];
+ assertNotNull(overlay);
verify(mWindowManager, times(1)).addView(
- eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()), any());
+ eq(overlay.getRootView()), any());
+ if (visibilityIfExist != null) {
+ assertEquals(visibilityIfExist.intValue(), overlay.getRootView().getVisibility());
+ }
} else {
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
}
@@ -330,7 +362,7 @@
mScreenDecorations.start();
// No views added.
- verifyOverlaysExistAndAdded(false, false, false, false);
+ verifyOverlaysExistAndAdded(false, false, false, false, null);
// No Tuners tuned.
verify(mTunerService, never()).addTunable(any(), any());
// No dot controller init
@@ -348,9 +380,12 @@
mScreenDecorations.start();
- // Top and bottom windows are created for privacy dot.
+ // Top and bottom windows are created with INVISIBLE because of privacy dot only
// Left and right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(
+ mScreenDecorations.mPrivacyDotShowingListener);
// Rounded corner views shall not exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
@@ -379,7 +414,7 @@
// Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -407,7 +442,9 @@
// Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -522,7 +559,7 @@
mScreenDecorations.start();
// Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -556,7 +593,9 @@
mScreenDecorations.start();
// Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -591,7 +630,7 @@
mScreenDecorations.start();
// Top window is created for top cutout.
// Bottom, left, or right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, false);
+ verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
// Privacy dots shall not exist because of no privacy
verifyDotViewsNullable(true);
@@ -615,7 +654,9 @@
// Top window is created for top cutout.
// Bottom window is created for privacy dot.
// Left or right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Top rounded corner views shall exist because of cutout
// but be gone because of no rounded corner
@@ -646,7 +687,7 @@
mScreenDecorations.start();
// Left window is created for left cutout.
// Bottom, top, or right window should be null.
- verifyOverlaysExistAndAdded(true, false, false, false);
+ verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
// Left rounded corner views shall exist because of cutout
// but be gone because of no rounded corner
@@ -674,7 +715,9 @@
// Left window is created for left cutout.
// Right window is created for privacy.
// Bottom, or top window should be null.
- verifyOverlaysExistAndAdded(true, false, true, false);
+ verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE);
@@ -699,7 +742,7 @@
// Top window is created for rounded corner and top cutout.
// Bottom window is created for rounded corner.
// Left, or right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -727,7 +770,9 @@
// Top window is created for rounded corner and top cutout.
// Bottom window is created for rounded corner.
// Left, or right window should be null.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
@@ -756,7 +801,7 @@
// Left window is created for rounded corner and left cutout.
// Right window is created for rounded corner.
// Top, or bottom window should be null.
- verifyOverlaysExistAndAdded(true, false, true, false);
+ verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
}
@Test
@@ -774,7 +819,9 @@
// Left window is created for rounded corner, left cutout, and privacy.
// Right window is created for rounded corner and privacy dot.
// Top, or bottom window should be null.
- verifyOverlaysExistAndAdded(true, false, true, false);
+ verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
}
@Test
@@ -793,7 +840,7 @@
// Bottom window is created for rounded corner.
// Left window is created for left cutout.
// Right window should be null.
- verifyOverlaysExistAndAdded(true, true, false, true);
+ verifyOverlaysExistAndAdded(true, true, false, true, View.VISIBLE);
}
@Test
@@ -812,7 +859,9 @@
// Bottom window is created for rounded corner.
// Left window is created for left cutout.
// Right window should be null.
- verifyOverlaysExistAndAdded(true, true, false, true);
+ verifyOverlaysExistAndAdded(true, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
}
@Test
@@ -827,7 +876,7 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- verifyOverlaysExistAndAdded(false, true, false, false);
+ verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
// Switch to long edge cutout(left).
final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -835,7 +884,7 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.onConfigurationChanged(new Configuration());
- verifyOverlaysExistAndAdded(true, false, false, false);
+ verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
}
@Test
@@ -850,7 +899,9 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
// Switch to long edge cutout(left).
final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null};
@@ -858,7 +909,9 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.onConfigurationChanged(new Configuration());
- verifyOverlaysExistAndAdded(true, false, true, false);
+ verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
+ verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(2)).setShowingListener(null);
// Verify each privacy dot id appears only once
mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
@@ -889,7 +942,7 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- assertNull(mScreenDecorations.mOverlays);
+ verifyOverlaysExistAndAdded(false, false, false, false, null);
when(mContext.getResources().getBoolean(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout))
@@ -897,7 +950,7 @@
mScreenDecorations.onConfigurationChanged(new Configuration());
// Only top windows should be added.
- verifyOverlaysExistAndAdded(false, true, false, false);
+ verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
}
@Test
@@ -912,23 +965,27 @@
.when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Both top and bottom windows should be added because of privacy dot,
- // but their visibility shall be gone because of no rounding.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ // Both top and bottom windows should be added with INVISIBLE because of only privacy dot,
+ // but rounded corners visibility shall be gone because of no rounding.
+ verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(
+ mScreenDecorations.mPrivacyDotShowingListener);
when(mContext.getResources().getBoolean(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout))
.thenReturn(true);
mScreenDecorations.onConfigurationChanged(new Configuration());
- assertNotNull(mScreenDecorations.mOverlays);
- // Both top and bottom windows should be added because of privacy dot,
- // but their visibility shall be gone because of no rounding.
- verifyOverlaysExistAndAdded(false, true, false, true);
+ // Both top and bottom windows should be added with VISIBLE because of privacy dot and
+ // cutout, but rounded corners visibility shall be gone because of no rounding.
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+ verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(null);
}
@Test
@@ -1043,9 +1100,7 @@
mScreenDecorations.start();
// should only inflate mOverlays when the hwc doesn't support screen decoration
assertNull(mScreenDecorations.mScreenDecorHwcWindow);
- assertNotNull(mScreenDecorations.mOverlays);
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
decorationSupport.format = PixelFormat.R_8;
@@ -1056,7 +1111,7 @@
// should only inflate hwc layer when the hwc supports screen decoration
assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
- assertNull(mScreenDecorations.mOverlays);
+ verifyOverlaysExistAndAdded(false, false, false, false, null);
}
@Test
@@ -1076,7 +1131,7 @@
mScreenDecorations.start();
// should only inflate hwc layer when the hwc supports screen decoration
assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
- assertNull(mScreenDecorations.mOverlays);
+ verifyOverlaysExistAndAdded(false, false, false, false, null);
doReturn(null).when(mDisplay).getDisplayDecorationSupport();
// Trigger the support hwc screen decoration change by changing the display unique id
@@ -1085,9 +1140,66 @@
// should only inflate mOverlays when the hwc doesn't support screen decoration
assertNull(mScreenDecorations.mScreenDecorHwcWindow);
- assertNotNull(mScreenDecorations.mOverlays);
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
- assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ }
+
+ @Test
+ public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
+ mPrivacyDotShowingListener.onPrivacyDotShown(null);
+ mPrivacyDotShowingListener.onPrivacyDotHidden(null);
+ }
+
+ @Test
+ public void testAutoShowHideOverlayWindowWhenSupportHwcLayer() {
+ setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+ final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
+ decorationSupport.format = PixelFormat.R_8;
+ doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer
+ verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
+
+ // Make sure view found and window visibility changed as well
+ final View view = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
+ .findViewById(R.id.privacy_dot_bottom_right_container);
+ mPrivacyDotShowingListener.onPrivacyDotShown(view);
+ assertEquals(View.VISIBLE,
+ mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+ mPrivacyDotShowingListener.onPrivacyDotHidden(view);
+ assertEquals(View.INVISIBLE,
+ mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+ }
+
+ @Test
+ public void testAutoShowHideOverlayWindowWhenNoRoundedAndNoCutout() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer
+ verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
+
+ // Make sure view found and window visibility changed as well
+ final View view = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
+ .findViewById(R.id.privacy_dot_bottom_right_container);
+ mPrivacyDotShowingListener.onPrivacyDotShown(view);
+ assertEquals(View.VISIBLE,
+ mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
+ mPrivacyDotShowingListener.onPrivacyDotHidden(view);
+ assertEquals(View.INVISIBLE,
+ mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView().getVisibility());
}
@Test
@@ -1108,7 +1220,7 @@
// Should only inflate hwc layer.
assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
- assertNull(mScreenDecorations.mOverlays);
+ verifyOverlaysExistAndAdded(false, false, false, false, null);
}
@Test
@@ -1128,13 +1240,11 @@
mScreenDecorations.start();
assertNotNull(mScreenDecorations.mScreenDecorHwcWindow);
- // mOverlays are inflated but the visibility should be GONE.
- assertNotNull(mScreenDecorations.mOverlays);
- final View topOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView();
- final View botOverlay = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView();
- assertEquals(topOverlay.getVisibility(), View.INVISIBLE);
- assertEquals(botOverlay.getVisibility(), View.INVISIBLE);
-
+ // mOverlays are inflated but the visibility should be INVISIBLE.
+ verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
+ verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
+ verify(mDotViewController, times(1)).setShowingListener(
+ mScreenDecorations.mPrivacyDotShowingListener);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index a016a1d..e175af7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.Region;
@@ -71,7 +72,6 @@
@Mock
FlingAnimationUtils mFlingAnimationUtils;
-
@Mock
FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -301,6 +301,8 @@
swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mValueAnimator, never()).addListener(any());
+
verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator),
eq(SCREEN_HEIGHT_PX * expansion),
eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN),
@@ -321,11 +323,20 @@
swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE));
+
+ ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
+ ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
+ verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
+ AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
+
verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE),
eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
verify(mValueAnimator).start();
verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
+
+ endAnimationListener.onAnimationEnd(mValueAnimator);
+ verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
}
/**
@@ -343,6 +354,8 @@
verify(mValueAnimatorCreator).create(eq(swipeDownPercentage),
eq(KeyguardBouncer.EXPANSION_VISIBLE));
+ verify(mValueAnimator, never()).addListener(any());
+
verify(mFlingAnimationUtils).apply(eq(mValueAnimator),
eq(SCREEN_HEIGHT_PX * swipeDownPercentage),
eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE),
@@ -367,6 +380,8 @@
verify(mValueAnimatorCreator).create(eq(swipeDownPercentage),
eq(KeyguardBouncer.EXPANSION_HIDDEN));
+ verify(mValueAnimator, never()).addListener(any());
+
verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator),
eq(SCREEN_HEIGHT_PX * swipeDownPercentage),
eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_HIDDEN),
@@ -389,11 +404,20 @@
swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
verify(mValueAnimatorCreator).create(eq(expansion), eq(KeyguardBouncer.EXPANSION_VISIBLE));
+
+ ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
+ ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
+ verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
+ AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
+
verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
eq(SCREEN_HEIGHT_PX * KeyguardBouncer.EXPANSION_VISIBLE),
eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
verify(mValueAnimator).start();
verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
+
+ endAnimationListener.onAnimationEnd(mValueAnimator);
+ verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
}
private void swipeToPosition(float percent, Direction direction, float velocityY) {
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 dc48eb0..14db4bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -413,11 +413,53 @@
listener.onScrubbingChanged(true)
mainExecutor.runAllReady()
+ verify(expandedSet, never()).setVisibility(eq(R.id.actionPrev), anyInt())
+ verify(expandedSet, never()).setVisibility(eq(R.id.actionNext), anyInt())
verify(expandedSet, never()).setVisibility(eq(R.id.media_scrubbing_elapsed_time), anyInt())
verify(expandedSet, never()).setVisibility(eq(R.id.media_scrubbing_total_time), anyInt())
}
@Test
+ fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
+ val icon = context.getDrawable(android.R.drawable.ic_media_play)
+ val semanticActions = MediaButton(
+ prevOrCustom = null,
+ nextOrCustom = MediaAction(icon, {}, "next", null),
+ )
+ val state = mediaData.copy(semanticActions = semanticActions)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ reset(expandedSet)
+
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ verify(expandedSet).setVisibility(R.id.actionNext, View.VISIBLE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_elapsed_time, View.GONE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_total_time, View.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
+ val icon = context.getDrawable(android.R.drawable.ic_media_play)
+ val semanticActions = MediaButton(
+ prevOrCustom = MediaAction(icon, {}, "prev", null),
+ nextOrCustom = null,
+ )
+ val state = mediaData.copy(semanticActions = semanticActions)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ reset(expandedSet)
+
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ verify(expandedSet).setVisibility(R.id.actionPrev, View.VISIBLE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_elapsed_time, View.GONE)
+ verify(expandedSet).setVisibility(R.id.media_scrubbing_total_time, View.GONE)
+ }
+
+ @Test
fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val semanticActions = MediaButton(
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 1921cb6..68eebed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.media
+import android.app.Notification
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
@@ -621,6 +622,36 @@
}
@Test
+ fun testTooManyNotificationActions_isTruncated() {
+ // GIVEN a notification where too many notification actions are added
+ val action = Notification.Action(R.drawable.ic_android, "action", null)
+ val notif = SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ })
+ for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
+ it.addAction(action)
+ }
+ }
+ build()
+ }
+
+ // WHEN the notification is loaded
+ mediaDataManager.onNotificationAdded(KEY, notif)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0), eq(false))
+ assertThat(mediaDataCaptor.value.actions.size).isEqualTo(
+ MediaDataManager.MAX_NOTIFICATION_ACTIONS)
+ }
+
+ @Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
@@ -781,6 +812,25 @@
}
@Test
+ fun testPlaybackActions_playPause_hasButton() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY_PAUSE
+ val stateBuilder = PlaybackState.Builder().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_play))
+ actions.playOrPause!!.action!!.run()
+ verify(transportControls).play()
+ }
+
+ @Test
fun testPlaybackLocationChange_isLogged() {
// Media control added for local playback
addNotificationAndLoad()
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 35d0024..642e29b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs
+import android.content.Intent
import android.os.Handler
import android.os.UserManager
import android.provider.Settings
@@ -14,6 +15,7 @@
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
@@ -137,11 +139,24 @@
}
@Test
+ fun testSettings() {
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ view.findViewById<View>(R.id.settings_button_container).performClick()
+
+ verify(activityStarter)
+ .startActivity(capture(captor), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
+
+ assertThat(captor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
+ }
+
+ @Test
fun testSettings_UserNotSetup() {
whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
- view.findViewById<View>(R.id.settings_button).performClick()
+ view.findViewById<View>(R.id.settings_button_container).performClick()
// Verify Settings wasn't launched.
- verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
+ verify(activityStarter, never())
+ .startActivity(any(), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 497a857..dfd70a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -57,7 +57,7 @@
.thenReturn(GAP_HEIGHT)
with(testableResources) {
addOverride(R.integer.keyguard_max_notification_count, -1)
- addOverride(R.dimen.notification_divider_height, NOTIFICATION_PADDING.toInt())
+ addOverride(R.dimen.notification_divider_height, DIVIDER_HEIGHT.toInt())
}
sizeCalculator =
@@ -109,7 +109,7 @@
fun computeMaxKeyguardNotifications_spaceForOne_shelfUsableForLastNotification_returnsTwo() {
val rowHeight = ROW_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight
- val shelfHeight = totalSpaceForEachRow + NOTIFICATION_PADDING
+ val shelfHeight = totalSpaceForEachRow + DIVIDER_HEIGHT
val spaceForOne = totalSpaceForEachRow
val rows =
listOf(
@@ -127,7 +127,7 @@
fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
val rowHeight = ROW_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight
- val spaceForTwo = totalSpaceForEachRow * 2 + NOTIFICATION_PADDING
+ val spaceForTwo = totalSpaceForEachRow * 2 + DIVIDER_HEIGHT
val rows =
listOf(
createMockRow(rowHeight),
@@ -143,7 +143,7 @@
fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() {
val rowHeight = ROW_HEIGHT
val shelfHeight = SHELF_HEIGHT
- val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
+ val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + DIVIDER_HEIGHT
val availableSpace = totalSpaceForEachRow * 2
// All rows in separate sections (default setup).
@@ -164,7 +164,7 @@
fun computeHeight_returnsAtMostSpaceAvailable_noGapBeforeShelf() {
val rowHeight = ROW_HEIGHT
val shelfHeight = SHELF_HEIGHT
- val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
+ val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + DIVIDER_HEIGHT
val availableSpace = totalSpaceForEachRow * 1
// Both rows are in the same section.
@@ -223,7 +223,7 @@
/** Default dimensions for tests that don't overwrite them. */
companion object {
const val GAP_HEIGHT = 12f
- const val NOTIFICATION_PADDING = 3f
+ const val DIVIDER_HEIGHT = 3f
const val SHELF_HEIGHT = 14f
const val ROW_HEIGHT = SHELF_HEIGHT * 3
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 0bcc3af..189aa0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -45,15 +45,18 @@
@Mock
private DarkIntensityApplier mApplier;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+
private LightBarTransitionsController mLightBarTransitionsController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(KeyguardStateController.class);
- mDependency.injectMockDependency(StatusBarStateController.class);
mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier,
- new CommandQueue(mContext));
+ new CommandQueue(mContext), mKeyguardStateController, mStatusBarStateController);
}
@Test
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 6846b2e..2cf0e3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1329,13 +1329,28 @@
if (mState.isServiceDetectingGestures() && mState.isTouchInteracting()) {
// Cancel without deleting events.
mHandler.removeCallbacks(mSendHoverEnterAndMoveDelayed);
- final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ if (pointerId == INVALID_POINTER_ID) {
+ MotionEvent event = mState.getLastReceivedEvent();
+ if (event != null) {
+ // Use the first pointer of the most recent event.
+ pointerId = event.getPointerId(0);
+ }
+ }
+ if (pointerId == INVALID_POINTER_ID) {
+ Slog.e(LOG_TAG, "Unable to find a valid pointer for touch exploration.");
+ return;
+ }
final int pointerIdBits = (1 << pointerId);
final int policyFlags = mState.getLastReceivedPolicyFlags();
mSendHoverEnterAndMoveDelayed.setPointerIdBits(pointerIdBits);
mSendHoverEnterAndMoveDelayed.setPolicyFlags(policyFlags);
mSendHoverEnterAndMoveDelayed.run();
mSendHoverEnterAndMoveDelayed.clear();
+ if (mReceivedPointerTracker.getReceivedPointerDownCount() == 0) {
+ // We need to send hover exit because there will be no future ACTION_UP
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+ }
}
}
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
index 8e0e395..2eae6af 100644
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
@@ -197,7 +197,9 @@
Slog.d(TAG, "onDestroyLocked(): requestId=" + requestId);
}
final CloudSearchCallbackInfo sessionInfo = mCallbackQueue.removeElement(requestId);
- sessionInfo.destroy();
+ if (sessionInfo != null) {
+ sessionInfo.destroy();
+ }
}
@Override
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index cb28254..bd2a97d 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -23,6 +23,7 @@
import android.companion.AssociationInfo;
import android.net.MacAddress;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -80,7 +81,7 @@
synchronized (mLock) {
if (mIdMap.containsKey(id)) {
- if (DEBUG) Log.w(TAG, "Association already stored.");
+ Slog.e(TAG, "Association with id " + id + " already exists.");
return;
}
mIdMap.put(id, association);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 13a5a28..02a0a95 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -813,7 +813,13 @@
synchronized (mPreviouslyUsedIds) {
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
- for (AssociationInfo it : mAssociationStore.getAssociationsForUser(userId)) {
+
+ // We should really only be checking associations for the given user (i.e.:
+ // mAssociationStore.getAssociationsForUser(userId)), BUT in the past we've got in a
+ // state where association IDs were not assigned correctly in regard to
+ // user-to-association-ids-range (e.g. associations with IDs from 1 to 100,000 should
+ // always belong to u0), so let's check all the associations.
+ for (AssociationInfo it : mAssociationStore.getAssociations()) {
usedIds.put(it.getId(), true);
}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index d0cc122..3639389 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -25,6 +25,8 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
+import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
import static com.android.server.companion.DataStoreUtils.isEndOfTag;
import static com.android.server.companion.DataStoreUtils.isStartOfTag;
@@ -194,7 +196,25 @@
// Associations for all users are stored in a single "flat" set: so we read directly
// into it.
- readStateForUser(userId, allAssociationsOut, previouslyUsedIds);
+ final Set<AssociationInfo> associationsForUser = new HashSet<>();
+ readStateForUser(userId, associationsForUser, previouslyUsedIds);
+
+ // Go through all the associations for the user and check if their IDs are within
+ // the allowed range (for the user).
+ final int firstAllowedId = getFirstAssociationIdForUser(userId);
+ final int lastAllowedId = getLastAssociationIdForUser(userId);
+ for (AssociationInfo association : associationsForUser) {
+ final int id = association.getId();
+ if (id < firstAllowedId || id > lastAllowedId) {
+ Slog.e(TAG, "Wrong association ID assignment: " + id + ". "
+ + "Association belongs to u" + userId + " and thus its ID should be "
+ + "within [" + firstAllowedId + ", " + lastAllowedId + "] range.");
+ // TODO(b/224736262): try fixing (re-assigning) the ID?
+ }
+ }
+
+ // Add user's association to the "output" set.
+ allAssociationsOut.addAll(associationsForUser);
// Save previously used IDs for this user into the "out" structure.
previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds);
@@ -369,7 +389,7 @@
// existing ones from the backup files. And the fact that we are reading from a V0 file,
// means that CDM hasn't assigned any IDs yet, so we can just start from the first available
// id for each user (eg. 1 for user 0; 100 001 - for user 1; 200 001 - for user 2; etc).
- int associationId = CompanionDeviceManagerService.getFirstAssociationIdForUser(userId);
+ int associationId = getFirstAssociationIdForUser(userId);
while (true) {
parser.nextTag();
if (isEndOfTag(parser, XML_TAG_ASSOCIATIONS)) break;
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index f4c24a8..27de8cd 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -76,6 +76,10 @@
@NonNull
private final ArraySet<UserHandle> mAllowedUsers;
@Nullable
+ private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
+ @Nullable
+ private final ArraySet<ComponentName> mBlockedCrossTaskNavigations;
+ @Nullable
private final ArraySet<ComponentName> mAllowedActivities;
@Nullable
private final ArraySet<ComponentName> mBlockedActivities;
@@ -100,6 +104,10 @@
* @param windowFlags The window flags that this controller is interested in.
* @param systemWindowFlags The system window flags that this controller is interested in.
* @param allowedUsers The set of users that are allowed to stream in this display.
+ * @param allowedCrossTaskNavigations The set of components explicitly allowed to navigate
+ * across tasks on this device.
+ * @param blockedCrossTaskNavigations The set of components explicitly blocked from
+ * navigating across tasks on this device.
* @param allowedActivities The set of activities explicitly allowed to stream on this device.
* Used only if the {@code activityPolicy} is
* {@link VirtualDeviceParams#ACTIVITY_POLICY_DEFAULT_BLOCKED}.
@@ -115,6 +123,8 @@
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
+ @NonNull Set<ComponentName> allowedCrossTaskNavigations,
+ @NonNull Set<ComponentName> blockedCrossTaskNavigations,
@NonNull Set<ComponentName> allowedActivities,
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@@ -122,6 +132,8 @@
@NonNull Consumer<ActivityInfo> activityBlockedCallback) {
super();
mAllowedUsers = allowedUsers;
+ mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
+ mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
mAllowedActivities = new ArraySet<>(allowedActivities);
mBlockedActivities = new ArraySet<>(blockedActivities);
mDefaultActivityPolicy = defaultActivityPolicy;
@@ -154,6 +166,46 @@
}
@Override
+ public boolean canActivityBeLaunched(ActivityInfo activityInfo,
+ @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+ boolean isNewTask) {
+ if (!isWindowingModeSupported(windowingMode)) {
+ return false;
+ }
+
+ final ComponentName activityComponent = activityInfo.getComponentName();
+ if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent)) {
+ // The error dialog alerting users that streaming is blocked is always allowed.
+ return true;
+ }
+
+ if (!canContainActivity(activityInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
+ mActivityBlockedCallback.accept(activityInfo);
+ return false;
+ }
+
+ if (launchingFromDisplayId == Display.DEFAULT_DISPLAY) {
+ return true;
+ }
+ if (isNewTask && !mBlockedCrossTaskNavigations.isEmpty()
+ && mBlockedCrossTaskNavigations.contains(activityComponent)) {
+ Slog.d(TAG, "Virtual device blocking cross task navigation of " + activityComponent);
+ mActivityBlockedCallback.accept(activityInfo);
+ return false;
+ }
+ if (isNewTask && !mAllowedCrossTaskNavigations.isEmpty()
+ && !mAllowedCrossTaskNavigations.contains(activityComponent)) {
+ Slog.d(TAG, "Virtual device not allowing cross task navigation of "
+ + activityComponent);
+ mActivityBlockedCallback.accept(activityInfo);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ @Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index e4b839a..90c879a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -283,6 +283,11 @@
return mVirtualAudioController;
}
+ @VisibleForTesting
+ SparseArray<GenericWindowPolicyController> getWindowPolicyControllersForTesting() {
+ return mWindowPolicyControllers;
+ }
+
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
public void onAudioSessionStarting(int displayId,
@@ -528,6 +533,8 @@
new GenericWindowPolicyController(FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
getAllowedUserHandles(),
+ mParams.getAllowedCrossTaskNavigations(),
+ mParams.getBlockedCrossTaskNavigations(),
mParams.getAllowedActivities(),
mParams.getBlockedActivities(),
mParams.getDefaultActivityPolicy(),
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ba1a199..2fd198b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -49,6 +49,7 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.os.FactoryTest.FACTORY_TEST_OFF;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -555,7 +556,7 @@
// Max character limit for a notification title. If the notification title is larger than this
// the notification will not be legible to the user.
- private static final int MAX_BUGREPORT_TITLE_SIZE = 50;
+ private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150;
private static final int NATIVE_DUMP_TIMEOUT_MS =
@@ -1477,6 +1478,9 @@
final UidObserverController mUidObserverController;
private volatile IUidObserver mNetworkPolicyUidObserver;
+ @GuardedBy("mUidNetworkBlockedReasons")
+ private final SparseIntArray mUidNetworkBlockedReasons = new SparseIntArray();
+
final AppRestrictionController mAppRestrictionController;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -15257,7 +15261,6 @@
uid, change, procState, procStateSeq, capability, ephemeral);
if (uidRec != null) {
uidRec.setLastReportedChange(enqueuedChange);
- uidRec.updateLastDispatchedProcStateSeq(enqueuedChange);
}
// Directly update the power manager, since we sit on top of it and it is critical
@@ -16523,18 +16526,13 @@
return;
}
record.lastNetworkUpdatedProcStateSeq = procStateSeq;
- if (record.curProcStateSeq > procStateSeq) {
- if (DEBUG_NETWORK) {
- Slog.d(TAG_NETWORK, "No need to handle older seq no., Uid: " + uid
- + ", curProcstateSeq: " + record.curProcStateSeq
- + ", procStateSeq: " + procStateSeq);
- }
- return;
- }
- if (record.waitingForNetwork) {
+ if (record.procStateSeqWaitingForNetwork != 0
+ && procStateSeq >= record.procStateSeqWaitingForNetwork) {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "Notifying all blocking threads for uid: " + uid
- + ", procStateSeq: " + procStateSeq);
+ + ", procStateSeq: " + procStateSeq
+ + ", procStateSeqWaitingForNetwork: "
+ + record.procStateSeqWaitingForNetwork);
}
record.networkStateLock.notifyAll();
}
@@ -16542,6 +16540,17 @@
}
@Override
+ public void onUidBlockedReasonsChanged(int uid, int blockedReasons) {
+ synchronized (mUidNetworkBlockedReasons) {
+ if (blockedReasons == BLOCKED_REASON_NONE) {
+ mUidNetworkBlockedReasons.delete(uid);
+ } else {
+ mUidNetworkBlockedReasons.put(uid, blockedReasons);
+ }
+ }
+ }
+
+ @Override
public boolean isRuntimeRestarted() {
return mSystemServiceManager.isRuntimeRestarted();
}
@@ -17203,17 +17212,17 @@
}
@Override
- public int getInstrumentationSourceUid(int uid) {
+ public boolean isUidCurrentlyInstrumented(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 activeInst.mSourceUid;
+ return true;
}
}
}
- return INVALID_UID;
+ return false;
}
@Override
@@ -17279,7 +17288,7 @@
if (mNetworkPolicyUidObserver != null) {
try {
mNetworkPolicyUidObserver.onUidStateChanged(uid, PROCESS_STATE_TOP,
- mProcessList.getProcStateSeqCounter(), PROCESS_CAPABILITY_ALL);
+ mProcessList.getNextProcStateSeq(), PROCESS_CAPABILITY_ALL);
} catch (RemoteException e) {
// Should not happen; call is within the same process
}
@@ -17568,23 +17577,6 @@
}
}
synchronized (record.networkStateLock) {
- if (record.lastDispatchedProcStateSeq < procStateSeq) {
- if (DEBUG_NETWORK) {
- Slog.d(TAG_NETWORK, "Uid state change for seq no. " + procStateSeq + " is not "
- + "dispatched to NPMS yet, so don't wait. Uid: " + callingUid
- + " lastProcStateSeqDispatchedToObservers: "
- + record.lastDispatchedProcStateSeq);
- }
- return;
- }
- if (record.curProcStateSeq > procStateSeq) {
- if (DEBUG_NETWORK) {
- Slog.d(TAG_NETWORK, "Ignore the wait requests for older seq numbers. Uid: "
- + callingUid + ", curProcStateSeq: " + record.curProcStateSeq
- + ", procStateSeq: " + procStateSeq);
- }
- return;
- }
if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "Network rules have been already updated for seq no. "
@@ -17600,9 +17592,9 @@
+ " Uid: " + callingUid + " procStateSeq: " + procStateSeq);
}
final long startTime = SystemClock.uptimeMillis();
- record.waitingForNetwork = true;
+ record.procStateSeqWaitingForNetwork = procStateSeq;
record.networkStateLock.wait(mWaitForNetworkTimeoutMs);
- record.waitingForNetwork = false;
+ record.procStateSeqWaitingForNetwork = 0;
final long totalTime = SystemClock.uptimeMillis() - startTime;
if (totalTime >= mWaitForNetworkTimeoutMs || DEBUG_NETWORK) {
Slog.w(TAG_NETWORK, "Total time waited for network rules to get updated: "
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ade44ea..0518899 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1918,7 +1918,7 @@
+ " completeLatency:" + completeLatency
+ " dispatchRealLatency:" + dispatchRealLatency
+ " completeRealLatency:" + completeRealLatency
- + " receiversSize:" + r.receivers.size()
+ + " receiversSize:" + numReceivers
+ " userId:" + r.userId
+ " userType:" + (userInfo != null? userInfo.userType : null));
FrameworkStatsLog.write(
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index bad7782..72a0e1a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4781,13 +4781,17 @@
}
/**
- * Checks if any uid is coming from background to foreground or vice versa and if so, increments
- * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
- * {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block.
+ * Increments the {@link UidRecord#curProcStateSeq} for all uids using global seq counter
+ * {@link ProcessList#mProcStateSeqCounter} and checks if any uid is coming
+ * from background to foreground or vice versa and if so, notifies the app if it needs to block.
*/
@VisibleForTesting
@GuardedBy(anyOf = {"mService", "mProcLock"})
void incrementProcStateSeqAndNotifyAppsLOSP(ActiveUids activeUids) {
+ for (int i = activeUids.size() - 1; i >= 0; --i) {
+ final UidRecord uidRec = activeUids.valueAt(i);
+ uidRec.curProcStateSeq = getNextProcStateSeq();
+ }
if (mService.mWaitForNetworkTimeoutMs <= 0) {
return;
}
@@ -4814,7 +4818,6 @@
continue;
}
synchronized (uidRec.networkStateLock) {
- uidRec.curProcStateSeq = ++mProcStateSeqCounter; // TODO: use method
if (blockState == NETWORK_STATE_BLOCK) {
if (blockingUids == null) {
blockingUids = new ArrayList<>();
@@ -4825,7 +4828,7 @@
Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
+ " threads for uid: " + uidRec);
}
- if (uidRec.waitingForNetwork) {
+ if (uidRec.procStateSeqWaitingForNetwork != 0) {
uidRec.networkStateLock.notifyAll();
}
}
@@ -4859,8 +4862,8 @@
}
}
- long getProcStateSeqCounter() {
- return mProcStateSeqCounter;
+ long getNextProcStateSeq() {
+ return ++mProcStateSeqCounter;
}
/**
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index e42dac4..e8c1b54 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -229,7 +229,6 @@
validateUid.setCurProcState(item.procState);
validateUid.setSetCapability(item.capability);
validateUid.setCurCapability(item.capability);
- validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
}
}
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 5c78d1e..51568d8 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -99,16 +99,9 @@
long lastNetworkUpdatedProcStateSeq;
/**
- * Last seq number for which AcitivityManagerService dispatched uid state change to
- * NetworkPolicyManagerService.
+ * Indicates if any thread is waiting for network rules to get updated for {@link #mUid}.
*/
- @GuardedBy("networkStateUpdate")
- long lastDispatchedProcStateSeq;
-
- /**
- * Indicates if any thread is waiting for network rules to get updated for {@link #uid}.
- */
- volatile boolean waitingForNetwork;
+ volatile long procStateSeqWaitingForNetwork;
/**
* Indicates whether this uid has internet permission or not.
@@ -345,18 +338,6 @@
mUid) == PackageManager.PERMISSION_GRANTED;
}
- /**
- * If the change being dispatched is not CHANGE_GONE (not interested in
- * these changes), then update the {@link #lastDispatchedProcStateSeq} with
- * {@link #curProcStateSeq}.
- */
- public void updateLastDispatchedProcStateSeq(int changeToDispatch) {
- if ((changeToDispatch & CHANGE_GONE) == 0) {
- lastDispatchedProcStateSeq = curProcStateSeq;
- }
- }
-
-
void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(UidRecordProto.UID, mUid);
@@ -377,7 +358,6 @@
proto.write(UidRecordProto.ProcStateSequence.CURURENT, curProcStateSeq);
proto.write(UidRecordProto.ProcStateSequence.LAST_NETWORK_UPDATED,
lastNetworkUpdatedProcStateSeq);
- proto.write(UidRecordProto.ProcStateSequence.LAST_DISPATCHED, lastDispatchedProcStateSeq);
proto.end(seqToken);
proto.end(token);
@@ -460,8 +440,6 @@
sb.append(curProcStateSeq);
sb.append(",");
sb.append(lastNetworkUpdatedProcStateSeq);
- sb.append(",");
- sb.append(lastDispatchedProcStateSeq);
sb.append(")}");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 752e17e..361629b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2367,8 +2367,7 @@
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
if (!isSelfRequest) {
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
boolean isCallerPermissionController;
try {
@@ -6895,8 +6894,7 @@
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
if (!isCallerSystem && !isCallerInstrumented) {
return null;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c88e3eb..67268e2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -63,6 +63,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -156,6 +157,8 @@
private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
+ private static final int RINGBUFFER_MAX = 100;
+
private final String TAG;
private final Object mLock = new Object();
@@ -213,6 +216,9 @@
private final float mScreenBrightnessDefault;
+ // Previously logged screen brightness. Used for autobrightness event dumpsys.
+ private float mPreviousScreenBrightness = Float.NaN;
+
// The minimum allowed brightness while in VR.
private final float mScreenBrightnessForVrRangeMinimum;
@@ -388,6 +394,9 @@
private final Runnable mOnBrightnessChangeRunnable;
+ // Used for keeping record in dumpsys for when and to which brightness auto adaptions were made.
+ private RingBuffer<AutobrightnessEvent> mAutobrightnessEventRingBuffer;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -981,6 +990,9 @@
mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
mDisplayDeviceConfig.getAmbientHorizonShort(),
mDisplayDeviceConfig.getAmbientHorizonLong());
+
+ mAutobrightnessEventRingBuffer =
+ new RingBuffer<>(AutobrightnessEvent.class, RINGBUFFER_MAX);
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -1555,6 +1567,15 @@
Slog.v(TAG, "Brightness [" + brightnessState + "] manual adjustment.");
}
+ // Add any automatic changes to autobrightness ringbuffer for dumpsys.
+ if (mBrightnessReason.reason == BrightnessReason.REASON_AUTOMATIC
+ && !BrightnessSynchronizer.floatEquals(
+ mPreviousScreenBrightness, brightnessState)) {
+ mPreviousScreenBrightness = brightnessState;
+ mAutobrightnessEventRingBuffer.append(new AutobrightnessEvent(
+ System.currentTimeMillis(), brightnessState));
+ }
+
// Update display white-balance.
if (mDisplayWhiteBalanceController != null) {
if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
@@ -2482,6 +2503,7 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.dump(pw);
+ dumpAutobrightnessEvents(pw);
}
if (mHbmController != null) {
@@ -2538,6 +2560,20 @@
}
}
+ private void dumpAutobrightnessEvents(PrintWriter pw) {
+ int size = mAutobrightnessEventRingBuffer.size();
+ if (size < 1) {
+ pw.println("No Automatic Brightness Adjustments");
+ return;
+ }
+
+ pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
+ AutobrightnessEvent[] eventArray = mAutobrightnessEventRingBuffer.toArray();
+ for (int i = 0; i < mAutobrightnessEventRingBuffer.size(); i++) {
+ pw.println(" " + eventArray[i].toString());
+ }
+ }
+
private static float clampAbsoluteBrightness(float value) {
return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
PowerManager.BRIGHTNESS_MAX);
@@ -2610,6 +2646,21 @@
}
}
+ private static class AutobrightnessEvent {
+ final long mTime;
+ final float mBrightness;
+
+ AutobrightnessEvent(long time, float brightness) {
+ mTime = time;
+ mBrightness = brightness;
+ }
+
+ @Override
+ public String toString() {
+ return TimeUtils.formatForLogging(mTime) + " - Brightness: " + mBrightness;
+ }
+ }
+
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 71afc40..8ab0b93 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -838,15 +837,7 @@
}
@Override // Binder call
- 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.
+ public boolean injectInputEvent(InputEvent event, int mode) {
Objects.requireNonNull(event, "event must not be null");
if (mode != InputEventInjectionSync.NONE
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -855,41 +846,22 @@
}
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 = mNative.injectInputEvent(event, injectIntoUid,
- targetUid, mode, INJECTION_TIMEOUT_MILLIS,
- WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ result = mNative.injectInputEvent(event, pid, uid, 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;
@@ -2725,12 +2697,8 @@
}
}
}
- private boolean checkCallingPermission(String permission, String func) {
- return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
- }
- private boolean checkCallingPermission(String permission, String func,
- boolean checkInstrumentationSource) {
+ private boolean checkCallingPermission(String permission, String func) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -2739,18 +2707,6 @@
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()
@@ -2996,6 +2952,13 @@
// 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);
}
@@ -3436,17 +3399,12 @@
@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) {
- mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
+ mNative.injectInputEvent(event, 0, 0,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
}
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 854170e..2169155 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -70,7 +70,7 @@
void setBlockUntrustedTouchesMode(int mode);
- int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode,
+ int injectInputEvent(InputEvent event, int pid, int uid, int syncMode,
int timeoutMillis, int policyFlags);
VerifiedInputEvent verifyInputEvent(InputEvent event);
@@ -237,8 +237,7 @@
public native void setBlockUntrustedTouchesMode(int mode);
@Override
- public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid,
- int syncMode,
+ public native int injectInputEvent(InputEvent event, int pid, int uid, int syncMode,
int timeoutMillis, int policyFlags);
@Override
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 4c265ad..f856193 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -192,8 +192,7 @@
ActivityManagerInternal ami = LocalServices.getService(
ActivityManagerInternal.class);
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
// 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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e5a6e65..001f956 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -28,6 +28,7 @@
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManager.isProcStateConsideredInteraction;
+import static android.app.ActivityManager.procStateToString;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
@@ -925,6 +926,7 @@
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
mAppStandby = LocalServices.getService(AppStandbyInternal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
@@ -1002,7 +1004,6 @@
}
}
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
try {
final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE
@@ -1119,7 +1120,9 @@
callbackInfo = new UidStateCallbackInfo();
mUidStateCallbackInfos.put(uid, callbackInfo);
}
- callbackInfo.update(uid, procState, procStateSeq, capability);
+ if (callbackInfo.procStateSeq == -1 || procStateSeq > callbackInfo.procStateSeq) {
+ callbackInfo.update(uid, procState, procStateSeq, capability);
+ }
if (!callbackInfo.isPending) {
mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo)
.sendToTarget();
@@ -1146,8 +1149,8 @@
private static final class UidStateCallbackInfo {
public int uid;
- public int procState;
- public long procStateSeq;
+ public int procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ public long procStateSeq = -1;
@ProcessCapability
public int capability;
public boolean isPending;
@@ -4044,13 +4047,22 @@
* {@link #updateRulesForPowerRestrictionsUL(int)}. Returns true if the state was updated.
*/
@GuardedBy("mUidRulesFirstLock")
- private boolean updateUidStateUL(int uid, int procState, @ProcessCapability int capability) {
+ private boolean updateUidStateUL(int uid, int procState, long procStateSeq,
+ @ProcessCapability int capability) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL");
try {
final UidState oldUidState = mUidState.get(uid);
+ if (oldUidState != null && procStateSeq < oldUidState.procStateSeq) {
+ if (LOGV) {
+ Slog.v(TAG, "Ignoring older uid state updates; uid=" + uid
+ + ",procState=" + procStateToString(procState) + ",seq=" + procStateSeq
+ + ",cap=" + capability + ",oldUidState=" + oldUidState);
+ }
+ return false;
+ }
if (oldUidState == null || oldUidState.procState != procState
|| oldUidState.capability != capability) {
- final UidState newUidState = new UidState(uid, procState, capability);
+ final UidState newUidState = new UidState(uid, procState, procStateSeq, capability);
// state changed, push updated rules
mUidState.put(uid, newUidState);
updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
@@ -4213,7 +4225,7 @@
: uidBlockedState.deriveUidRules();
}
if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
- postBlockedReasonsChangedMsg(uid,
+ handleBlockedReasonsChanged(uid,
newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
postUidRulesChangedMsg(uid, uidRules);
@@ -4575,6 +4587,9 @@
someArgs.argi2 = uidBlockedState.effectiveBlockedReasons;
someArgs.argi3 = uidBlockedState.deriveUidRules();
uidStateUpdates.append(uid, someArgs);
+ // TODO: Update the state for all changed uids together.
+ mActivityManagerInternal.onUidBlockedReasonsChanged(uid,
+ uidBlockedState.effectiveBlockedReasons);
}
}
}
@@ -4797,6 +4812,8 @@
synchronized (mUidBlockedState) {
mUidBlockedState.delete(uid);
}
+ mUidState.delete(uid);
+ mActivityManagerInternal.onUidBlockedReasonsChanged(uid, BLOCKED_REASON_NONE);
mUidPolicy.delete(uid);
mUidFirewallStandbyRules.delete(uid);
mUidFirewallDozableRules.delete(uid);
@@ -4959,7 +4976,7 @@
}
}
if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
- postBlockedReasonsChangedMsg(uid,
+ handleBlockedReasonsChanged(uid,
newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
postUidRulesChangedMsg(uid, uidRules);
@@ -5102,7 +5119,7 @@
: uidBlockedState.deriveUidRules();
}
if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
- postBlockedReasonsChangedMsg(uid,
+ handleBlockedReasonsChanged(uid,
newEffectiveBlockedReasons,
oldEffectiveBlockedReasons);
@@ -5135,6 +5152,12 @@
}
}
+ private void handleBlockedReasonsChanged(int uid, int newEffectiveBlockedReasons,
+ int oldEffectiveBlockedReasons) {
+ mActivityManagerInternal.onUidBlockedReasonsChanged(uid, newEffectiveBlockedReasons);
+ postBlockedReasonsChangedMsg(uid, newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
+ }
+
private void postBlockedReasonsChangedMsg(int uid, int newEffectiveBlockedReasons,
int oldEffectiveBlockedReasons) {
mHandler.obtainMessage(MSG_UID_BLOCKED_REASON_CHANGED, uid,
@@ -5438,21 +5461,7 @@
public boolean handleMessage(Message msg) {
switch (msg.what) {
case UID_MSG_STATE_CHANGED: {
- final UidStateCallbackInfo uidStateCallbackInfo =
- (UidStateCallbackInfo) msg.obj;
- final int uid;
- final int procState;
- final long procStateSeq;
- final int capability;
- synchronized (mUidStateCallbackInfos) {
- uid = uidStateCallbackInfo.uid;
- procState = uidStateCallbackInfo.procState;
- procStateSeq = uidStateCallbackInfo.procStateSeq;
- capability = uidStateCallbackInfo.capability;
- uidStateCallbackInfo.isPending = false;
- }
-
- handleUidChanged(uid, procState, procStateSeq, capability);
+ handleUidChanged((UidStateCallbackInfo) msg.obj);
return true;
}
case UID_MSG_GONE: {
@@ -5467,17 +5476,28 @@
}
};
- void handleUidChanged(int uid, int procState, long procStateSeq,
- @ProcessCapability int capability) {
+ void handleUidChanged(@NonNull UidStateCallbackInfo uidStateCallbackInfo) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
try {
boolean updated;
+ final int uid;
+ final int procState;
+ final long procStateSeq;
+ final int capability;
synchronized (mUidRulesFirstLock) {
+ synchronized (mUidStateCallbackInfos) {
+ uid = uidStateCallbackInfo.uid;
+ procState = uidStateCallbackInfo.procState;
+ procStateSeq = uidStateCallbackInfo.procStateSeq;
+ capability = uidStateCallbackInfo.capability;
+ uidStateCallbackInfo.isPending = false;
+ }
+
// We received a uid state change callback, add it to the history so that it
// will be useful for debugging.
mLogger.uidStateChanged(uid, procState, procStateSeq, capability);
// Now update the network policy rules as per the updated uid state.
- updated = updateUidStateUL(uid, procState, capability);
+ updated = updateUidStateUL(uid, procState, procStateSeq, capability);
// Updating the network rules is done, so notify AMS about this.
mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d72e0ba..dfc6fa9 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -135,7 +135,6 @@
import java.io.PrintWriter;
import java.text.DateFormat;
-import java.util.ArrayList;
import java.util.Date;
/**
@@ -2046,12 +2045,13 @@
final DisplayContent displayContent = mRootWindowContainer.getDisplayContentOrCreate(
mPreferredTaskDisplayArea.getDisplayId());
if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
- final ArrayList<ActivityInfo> activities = new ArrayList<>();
- activities.add(r.info);
final int targetWindowingMode = (targetTask != null)
? targetTask.getWindowingMode() : displayContent.getWindowingMode();
+ final int launchingFromDisplayId =
+ mSourceRecord != null ? mSourceRecord.getDisplayId() : DEFAULT_DISPLAY;
if (!displayContent.mDwpcHelper
- .canContainActivities(activities, targetWindowingMode)) {
+ .canActivityBeLaunched(r.info, targetWindowingMode, launchingFromDisplayId,
+ newTask)) {
Slog.w(TAG, "Abort to launch " + r.info.getComponentName()
+ " on display area " + mPreferredTaskDisplayArea);
return START_ABORTED;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 01dfb91..9bf69bc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -387,11 +387,16 @@
public abstract ComponentName getActivityName(IBinder activityToken);
/**
- * @return the activity token and IApplicationThread for the top activity in the task or null
- * if there isn't a top activity with a valid process.
+ * Returns non-finishing Activity that have a process attached for the given task and the token
+ * with the activity token and the IApplicationThread or null if there is no Activity with a
+ * valid process. Given the null token for the task will return the top Activity in the task.
+ *
+ * @param taskId the Activity task id.
+ * @param token the Activity token, set null if get top Activity for the given task id.
*/
@Nullable
- public abstract ActivityTokens getTopActivityForTask(int taskId);
+ public abstract ActivityTokens getAttachedNonFinishingActivityForTask(int taskId,
+ IBinder token);
public abstract IIntentSender getIntentSender(int type, String packageName,
@Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 14436bc..1f7c0ef 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2944,7 +2944,7 @@
final long callingId = Binder.clearCallingIdentity();
LocalService.ActivityTokens tokens = null;
try {
- tokens = mInternal.getTopActivityForTask(taskId);
+ tokens = mInternal.getAttachedNonFinishingActivityForTask(taskId, null);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -5804,7 +5804,8 @@
}
@Override
- public ActivityTokens getTopActivityForTask(int taskId) {
+ public ActivityTokens getAttachedNonFinishingActivityForTask(int taskId,
+ IBinder token) {
synchronized (mGlobalLock) {
final Task task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_ATTACHED_TASK_ONLY);
@@ -5813,19 +5814,30 @@
+ " Requested task not found");
return null;
}
- final ActivityRecord activity = task.getTopNonFinishingActivity();
- if (activity == null) {
- Slog.w(TAG, "getApplicationThreadForTopActivity failed:"
- + " Requested activity not found");
+ final List<ActivityRecord> list = new ArrayList<>();
+ task.forAllActivities(r -> {
+ if (!r.finishing) {
+ list.add(r);
+ }
+ });
+ if (list.size() <= 0) {
return null;
}
- if (!activity.attachedToProcess()) {
- Slog.w(TAG, "getApplicationThreadForTopActivity failed: No process for "
- + activity);
- return null;
+ // pass null, get top Activity
+ if (token == null && list.get(0).attachedToProcess()) {
+ ActivityRecord topRecord = list.get(0);
+ return new ActivityTokens(topRecord.token, topRecord.assistToken,
+ topRecord.app.getThread(), topRecord.shareableActivityToken);
}
- return new ActivityTokens(activity.token, activity.assistToken,
- activity.app.getThread(), activity.shareableActivityToken);
+ // find the expected Activity
+ for (int i = 0; i < list.size(); i++) {
+ ActivityRecord record = list.get(i);
+ if (record.shareableActivityToken == token && record.attachedToProcess()) {
+ return new ActivityTokens(record.token, record.assistToken,
+ record.app.getThread(), record.shareableActivityToken);
+ }
+ }
+ return null;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 27d46ec..2deb828 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -75,6 +75,19 @@
}
/**
+ * @see DisplayWindowPolicyController#canActivityBeLaunched(ActivityInfo, int, int, boolean)
+ */
+ public boolean canActivityBeLaunched(ActivityInfo activityInfo,
+ @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+ boolean isNewTask) {
+ if (mDisplayWindowPolicyController == null) {
+ return true;
+ }
+ return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, windowingMode,
+ launchingFromDisplayId, isNewTask);
+ }
+
+ /**
* @see DisplayWindowPolicyController#keepActivityOnWindowFlagsChanged(ActivityInfo, int, int)
*/
boolean keepActivityOnWindowFlagsChanged(ActivityInfo aInfo, int flagChanges,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 479ddce..3c5ebe7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,6 +106,7 @@
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
+ jmethodID checkInjectEventsPermission;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -332,6 +333,7 @@
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;
@@ -1369,6 +1371,19 @@
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();
@@ -1697,11 +1712,10 @@
}
static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj,
- jboolean injectIntoUid, jint uid, jint syncMode,
+ jint injectorPid, jint injectorUid, 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);
@@ -1714,7 +1728,8 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
+ injectorUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1727,8 +1742,8 @@
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
- mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
+ injectorUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -2333,7 +2348,7 @@
{"setMaximumObscuringOpacityForTouch", "(F)V",
(void*)nativeSetMaximumObscuringOpacityForTouch},
{"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode},
- {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent},
+ {"injectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*)nativeInjectInputEvent},
{"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
{"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
@@ -2480,6 +2495,9 @@
"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/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
index d74ff8f..760da4d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
@@ -179,4 +179,177 @@
assertTrue(hasTestEvent);
assertEquals(2, count);
}
+
+ /** Tests that the API works as expected even with the caching system. */
+ @Test
+ public void testQueryEarliestEventsForPackage_Caching() throws Exception {
+ final long forcedDiff = 5000;
+ Event event1 = new Event(NOTIFICATION_SEEN, SystemClock.elapsedRealtime());
+ event1.mPackage = TEST_PACKAGE_NAME;
+ mService.reportEvent(event1);
+ final long event1ReportTime = System.currentTimeMillis();
+ Thread.sleep(forcedDiff);
+ Event event2 = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
+ event2.mPackage = TEST_PACKAGE_NAME;
+ mService.reportEvent(event2);
+ final long event2ReportTime = System.currentTimeMillis();
+
+ // Force persist the events instead of waiting for them to be processed on the handler.
+ mService.persistActiveStats();
+
+ long now = System.currentTimeMillis();
+ long startTime = now - forcedDiff * 2;
+ UsageEvents events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ boolean hasTestEvent = false;
+ int count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertTrue(hasTestEvent);
+ assertEquals(2, count);
+
+ // Query again
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertTrue(hasTestEvent);
+ assertEquals(2, count);
+
+ // Query around just the first event
+ now = event1ReportTime;
+ startTime = now - forcedDiff * 2;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertFalse(hasTestEvent);
+ assertEquals(1, count);
+
+ // Shift query around the first event, still exclude the second
+ now = event1ReportTime + forcedDiff / 2;
+ startTime = event1ReportTime - forcedDiff / 2;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertFalse(hasTestEvent);
+ assertEquals(1, count);
+
+ // Shift query around the second event only
+ now = event2ReportTime + 1;
+ startTime = event1ReportTime + forcedDiff / 4;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertTrue(hasTestEvent);
+ assertEquals(1, count);
+
+ // Shift query around both events
+ now = event2ReportTime + 1;
+ startTime = now - forcedDiff * 2;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertTrue(hasTestEvent);
+ assertEquals(2, count);
+
+ // Query around just the first event and then shift end time to include second event
+ now = event1ReportTime;
+ startTime = now - forcedDiff * 2;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertFalse(hasTestEvent);
+ assertEquals(1, count);
+
+ now = event2ReportTime + 1;
+ events = mService.queryEarliestEventsForPackage(
+ startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
+
+ assertNotNull(events);
+ hasTestEvent = false;
+ count = 0;
+ while (events.hasNextEvent()) {
+ count++;
+ Event outEvent = new Event();
+ events.getNextEvent(outEvent);
+ if (outEvent.mEventType == ACTIVITY_RESUMED) {
+ hasTestEvent = true;
+ }
+ }
+ assertTrue(hasTestEvent);
+ assertEquals(2, count);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index 8c21a39..16406bc 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -129,7 +129,7 @@
thread2.assertWaiting("Unexpected state for " + record2);
thread2.interrupt();
- mAms.mProcessList.mActiveUids.clear();
+ clearActiveUids();
}
private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
@@ -137,11 +137,21 @@
final UidRecord record = new UidRecord(uid, mAms);
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
record.curProcStateSeq = curProcStateSeq;
- record.waitingForNetwork = true;
- mAms.mProcessList.mActiveUids.put(uid, record);
+ record.procStateSeqWaitingForNetwork = 1;
+ addActiveUidRecord(uid, record);
return record;
}
+ @SuppressWarnings("GuardedBy")
+ private void addActiveUidRecord(int uid, UidRecord record) {
+ mAms.mProcessList.mActiveUids.put(uid, record);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void clearActiveUids() {
+ mAms.mProcessList.mActiveUids.clear();
+ }
+
static class CustomThread extends Thread {
private static final long WAIT_TIMEOUT_MS = 1000;
private static final long WAIT_INTERVAL_MS = 100;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 36c37c4..c76964e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -196,8 +196,6 @@
verifySeqCounterAndInteractions(uidRec,
PROCESS_STATE_TOP, // prevState
PROCESS_STATE_TOP, // curState
- 0, // expectedGlobalCounter
- 0, // exptectedCurProcStateSeq
NETWORK_STATE_NO_CHANGE, // expectedBlockState
false); // expectNotify
@@ -205,8 +203,6 @@
verifySeqCounterAndInteractions(uidRec,
PROCESS_STATE_FOREGROUND_SERVICE, // prevState
PROCESS_STATE_SERVICE, // curState
- 1, // expectedGlobalCounter
- 1, // exptectedCurProcStateSeq
NETWORK_STATE_UNBLOCK, // expectedBlockState
true); // expectNotify
@@ -218,8 +214,6 @@
verifySeqCounterAndInteractions(uidRec,
PROCESS_STATE_TRANSIENT_BACKGROUND, // prevState
PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
- 42, // expectedGlobalCounter
- 1, // exptectedCurProcStateSeq
NETWORK_STATE_NO_CHANGE, // expectedBlockState
false); // expectNotify
@@ -227,73 +221,22 @@
verifySeqCounterAndInteractions(uidRec,
PROCESS_STATE_LAST_ACTIVITY, // prevState
PROCESS_STATE_TOP, // curState
- 43, // expectedGlobalCounter
- 43, // exptectedCurProcStateSeq
NETWORK_STATE_BLOCK, // expectedBlockState
false); // expectNotify
// verify waiting threads are not notified.
- uidRec.waitingForNetwork = false;
+ uidRec.procStateSeqWaitingForNetwork = 0;
// Uid state is moving from foreground to background.
verifySeqCounterAndInteractions(uidRec,
PROCESS_STATE_FOREGROUND_SERVICE, // prevState
PROCESS_STATE_SERVICE, // curState
- 44, // expectedGlobalCounter
- 44, // exptectedCurProcStateSeq
NETWORK_STATE_UNBLOCK, // expectedBlockState
false); // expectNotify
-
- // Verify when uid is not restricted, procStateSeq is not incremented.
- uidRec.waitingForNetwork = true;
- mInjector.setNetworkRestrictedForUid(false);
- verifySeqCounterAndInteractions(uidRec,
- PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
- PROCESS_STATE_TOP, // curState
- 44, // expectedGlobalCounter
- 44, // exptectedCurProcStateSeq
- -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
- false); // expectNotify
-
- // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented.
- mAms.mWaitForNetworkTimeoutMs = 0;
- mInjector.setNetworkRestrictedForUid(true);
- verifySeqCounterAndInteractions(uidRec,
- PROCESS_STATE_TOP, // prevState
- PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
- 44, // expectedGlobalCounter
- 44, // exptectedCurProcStateSeq
- -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
- false); // expectNotify
-
- // Verify when the uid doesn't have internet permission, then procStateSeq is not
- // incremented.
- uidRec.hasInternetPermission = false;
- mAms.mWaitForNetworkTimeoutMs = 111;
- mInjector.setNetworkRestrictedForUid(true);
- verifySeqCounterAndInteractions(uidRec,
- PROCESS_STATE_CACHED_ACTIVITY, // prevState
- PROCESS_STATE_FOREGROUND_SERVICE, // curState
- 44, // expectedGlobalCounter
- 44, // exptectedCurProcStateSeq
- -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
- false); // expectNotify
-
- // Verify procStateSeq is not incremented when the uid is not an application, regardless
- // of the process state.
- final int notAppUid = 111;
- final UidRecord uidRec2 = addUidRecord(notAppUid);
- verifySeqCounterAndInteractions(uidRec2,
- PROCESS_STATE_CACHED_EMPTY, // prevState
- PROCESS_STATE_TOP, // curState
- 44, // expectedGlobalCounter
- 0, // exptectedCurProcStateSeq
- -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
- false); // expectNotify
}
private UidRecord addUidRecord(int uid) {
final UidRecord uidRec = new UidRecord(uid, mAms);
- uidRec.waitingForNetwork = true;
+ uidRec.procStateSeqWaitingForNetwork = 1;
uidRec.hasInternetPermission = true;
mAms.mProcessList.mActiveUids.put(uid, uidRec);
@@ -310,18 +253,26 @@
@SuppressWarnings("GuardedBy")
private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
- int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState,
- boolean expectNotify) throws Exception {
+ int expectedBlockState, boolean expectNotify) throws Exception {
CustomThread thread = new CustomThread(uidRec.networkStateLock);
thread.startAndWait("Unexpected state for " + uidRec);
uidRec.setSetProcState(prevState);
uidRec.setCurProcState(curState);
+ final long beforeProcStateSeq = mAms.mProcessList.mProcStateSeqCounter;
+
mAms.mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(mAms.mProcessList.mActiveUids);
- // @SuppressWarnings("GuardedBy")
- assertEquals(expectedGlobalCounter, mAms.mProcessList.mProcStateSeqCounter);
- assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
+ final long afterProcStateSeq = beforeProcStateSeq
+ + mAms.mProcessList.mActiveUids.size();
+ assertEquals("beforeProcStateSeq=" + beforeProcStateSeq
+ + ",activeUids.size=" + mAms.mProcessList.mActiveUids.size(),
+ afterProcStateSeq, mAms.mProcessList.mProcStateSeqCounter);
+ assertTrue("beforeProcStateSeq=" + beforeProcStateSeq
+ + ",afterProcStateSeq=" + afterProcStateSeq
+ + ",uidCurProcStateSeq=" + uidRec.curProcStateSeq,
+ uidRec.curProcStateSeq > beforeProcStateSeq
+ && uidRec.curProcStateSeq <= afterProcStateSeq);
for (int i = mAms.mProcessList.getLruSizeLOSP() - 1; i >= 0; --i) {
final ProcessRecord app = mAms.mProcessList.getLruProcessesLOSP().get(i);
@@ -820,48 +771,11 @@
}
@Test
- public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
- final UidRecord uidRecord = new UidRecord(TEST_UID, mAms);
- uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
-
- // Verify with no pending changes for TEST_UID.
- verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
-
- // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
- // expected.
- uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
- verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
- }
-
- @Test
public void testEnqueueUidChangeLocked_nullUidRecord() {
// Use "null" uidRecord to make sure there is no crash.
mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
}
- private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
- // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
- for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
- final int changeToDispatch = UID_RECORD_CHANGES[i];
- // Reset lastProcStateSeqDispatchToObservers after every test.
- uidRecord.lastDispatchedProcStateSeq = 0;
- mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
- // Verify there is no effect on curProcStateSeq.
- assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
- if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) {
- // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
- // lastProcStateSeqDispatchedToObservers is not updated.
- assertNotEquals(uidRecord.curProcStateSeq,
- uidRecord.lastDispatchedProcStateSeq);
- } else {
- // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
- // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
- assertEquals(uidRecord.curProcStateSeq,
- uidRecord.lastDispatchedProcStateSeq);
- }
- }
- }
-
@MediumTest
@Test
public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
@@ -898,29 +812,10 @@
// Check there is no crash when there is no UidRecord for myUid
mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1);
- // Verify there is no waiting when UidRecord.curProcStateSeq is greater than
- // the procStateSeq in the request to wait.
- verifyWaitingForNetworkStateUpdate(
- TEST_PROC_STATE_SEQ1, // curProcStateSeq
- TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
- TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq
- TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait
- false); // expectWait
-
- // Verify there is no waiting when the procStateSeq in the request to wait is
- // not dispatched to NPMS.
- verifyWaitingForNetworkStateUpdate(
- TEST_PROC_STATE_SEQ1, // curProcStateSeq
- TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq
- TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
- TEST_PROC_STATE_SEQ1, // procStateSeqToWait
- false); // expectWait
-
// Verify there is not waiting when the procStateSeq in the request already has
// an updated network state.
verifyWaitingForNetworkStateUpdate(
TEST_PROC_STATE_SEQ1, // curProcStateSeq
- TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq
TEST_PROC_STATE_SEQ1, // procStateSeqToWait
false); // expectWait
@@ -928,18 +823,16 @@
// Verify waiting for network works
verifyWaitingForNetworkStateUpdate(
TEST_PROC_STATE_SEQ1, // curProcStateSeq
- TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
TEST_PROC_STATE_SEQ1, // procStateSeqToWait
true); // expectWait
}
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
- long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
+ long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
final UidRecord record = new UidRecord(Process.myUid(), mAms);
record.curProcStateSeq = curProcStateSeq;
- record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
mAms.mProcessList.mActiveUids.put(Process.myUid(), record);
@@ -958,7 +851,7 @@
}
thread.assertTerminated(errMsg);
assertTrue(thread.mNotified);
- assertFalse(record.waitingForNetwork);
+ assertEquals(0, record.procStateSeqWaitingForNetwork);
} else {
thread.start();
thread.assertTerminated(errMsg);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c96b4d6..d1b0156 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.companion.virtual;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -26,6 +28,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -36,14 +39,19 @@
import static org.testng.Assert.assertThrows;
import android.Manifest;
+import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
@@ -65,11 +73,13 @@
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.ArraySet;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import org.junit.Before;
@@ -80,11 +90,21 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@Presubmit
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualDeviceManagerServiceTest {
+ private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp";
+ private static final String PERMISSION_CONTROLLER_PACKAGE_NAME =
+ "com.android.permissioncontroller";
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+ private static final String VENDING_PACKAGE_NAME = "com.android.vending";
+ private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
+ private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String DEVICE_NAME = "device name";
private static final int DISPLAY_ID = 2;
private static final int PRODUCT_ID = 10;
@@ -94,10 +114,12 @@
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
private static final Binder BINDER = new Binder("binder");
+ private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
+ private AssociationInfo mAssociationInfo;
@Mock
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
@@ -119,6 +141,30 @@
private IAudioRoutingCallback mRoutingCallback;
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
+ @Mock
+ private ApplicationInfo mApplicationInfoMock;
+
+ private ArraySet<ComponentName> getBlockedActivities() {
+ ArraySet<ComponentName> blockedActivities = new ArraySet<>();
+ blockedActivities.add(new ComponentName(SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME));
+ blockedActivities.add(new ComponentName(VENDING_PACKAGE_NAME, VENDING_PACKAGE_NAME));
+ blockedActivities.add(
+ new ComponentName(GOOGLE_DIALER_PACKAGE_NAME, GOOGLE_DIALER_PACKAGE_NAME));
+ blockedActivities.add(
+ new ComponentName(GOOGLE_MAPS_PACKAGE_NAME, GOOGLE_MAPS_PACKAGE_NAME));
+ return blockedActivities;
+ }
+
+ private ArrayList<ActivityInfo> getActivityInfoList(
+ String packageName, String name, boolean displayOnRemoveDevices) {
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = packageName;
+ activityInfo.name = name;
+ activityInfo.flags = displayOnRemoveDevices
+ ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES;
+ activityInfo.applicationInfo = mApplicationInfoMock;
+ return new ArrayList<>(Arrays.asList(activityInfo));
+ }
@Before
public void setUp() {
@@ -151,13 +197,18 @@
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
mInputController = new InputController(new Object(), mNativeWrapperMock);
- AssociationInfo associationInfo = new AssociationInfo(1, 0, null,
+ mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
+
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- associationInfo, new Binder(), /* uid */ 0, mInputController,
+ mAssociationInfo, new Binder(), /* uid */ 0, mInputController,
(int associationId) -> {
}, mPendingTrampolineCallback, mActivityListener,
- new VirtualDeviceParams.Builder().build());
+ params);
}
@Test
@@ -590,4 +641,112 @@
mDeviceImpl.setShowPointerIcon(true);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(true), anyInt());
}
+
+ @Test
+ public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext, never()).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ PERMISSION_CONTROLLER_PACKAGE_NAME,
+ PERMISSION_CONTROLLER_PACKAGE_NAME, /* displayOnRemoveDevices */ false);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ SETTINGS_PACKAGE_NAME,
+ SETTINGS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ VENDING_PACKAGE_NAME,
+ VENDING_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ GOOGLE_DIALER_PACKAGE_NAME,
+ GOOGLE_DIALER_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
+
+ @Test
+ public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+
+ ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
+ GOOGLE_MAPS_PACKAGE_NAME,
+ GOOGLE_MAPS_PACKAGE_NAME, /* displayOnRemoveDevices */ true);
+ Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
+ activityInfos.get(0), mAssociationInfo.getDisplayName());
+ gwpc.canContainActivities(activityInfos, WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+
+ verify(mContext).startActivityAsUser(argThat(intent ->
+ intent.filterEquals(blockedAppIntent)), any(), any());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 75faf45..cd836c7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -75,6 +75,8 @@
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
/* allowedUsers= */ new ArraySet<>(),
+ /* allowedCrossTaskNavigations= */ new ArraySet<>(),
+ /* blockedCrossTaskNavigations= */ new ArraySet<>(),
/* allowedActivities= */ new ArraySet<>(),
/* blockedActivities= */ new ArraySet<>(),
VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 12fc958..b7faf22 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -17,6 +17,7 @@
package com.android.server.job;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -25,6 +26,7 @@
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.platform.test.annotations.LargeTest;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -118,6 +120,56 @@
}
@Test
+ public void testContains() {
+ JobStatus joba1 = createJobStatus("testRemove", createJobInfo(1), 1);
+ JobStatus joba2 = createJobStatus("testRemove", createJobInfo(2), 1);
+ JobStatus jobb1 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 2);
+ JobStatus jobb2 = createJobStatus("testRemove",
+ createJobInfo(4).setPriority(JobInfo.PRIORITY_MIN), 2);
+
+ // Make joba1 and joba2 sort-equivalent
+ joba1.enqueueTime = 3;
+ joba2.enqueueTime = 3;
+ jobb1.enqueueTime = 4;
+ jobb2.enqueueTime = 1;
+
+ PendingJobQueue jobQueue = new PendingJobQueue();
+
+ assertFalse(jobQueue.contains(joba1));
+ assertFalse(jobQueue.contains(joba2));
+ assertFalse(jobQueue.contains(jobb1));
+ assertFalse(jobQueue.contains(jobb2));
+
+ jobQueue.add(joba1);
+
+ assertTrue(jobQueue.contains(joba1));
+ assertFalse(jobQueue.contains(joba2));
+ assertFalse(jobQueue.contains(jobb1));
+ assertFalse(jobQueue.contains(jobb2));
+
+ jobQueue.add(jobb1);
+
+ assertTrue(jobQueue.contains(joba1));
+ assertFalse(jobQueue.contains(joba2));
+ assertTrue(jobQueue.contains(jobb1));
+ assertFalse(jobQueue.contains(jobb2));
+
+ jobQueue.add(jobb2);
+
+ assertTrue(jobQueue.contains(joba1));
+ assertFalse(jobQueue.contains(joba2));
+ assertTrue(jobQueue.contains(jobb1));
+ assertTrue(jobQueue.contains(jobb2));
+
+ jobQueue.add(joba2);
+
+ assertTrue(jobQueue.contains(joba1));
+ assertTrue(jobQueue.contains(joba2));
+ assertTrue(jobQueue.contains(jobb1));
+ assertTrue(jobQueue.contains(jobb2));
+ }
+
+ @Test
public void testRemove() {
List<JobStatus> jobs = new ArrayList<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
@@ -129,9 +181,120 @@
PendingJobQueue jobQueue = new PendingJobQueue();
jobQueue.addAll(jobs);
+ ArraySet<JobStatus> removed = new ArraySet<>();
+ JobStatus job;
for (int i = 0; i < jobs.size(); ++i) {
jobQueue.remove(jobs.get(i));
+ removed.add(jobs.get(i));
+
assertEquals(jobs.size() - i - 1, jobQueue.size());
+
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertFalse("Queue retained a removed job " + testJobToString(job),
+ removed.contains(job));
+ }
+ }
+ assertNull(jobQueue.next());
+ }
+
+ @Test
+ public void testRemove_outOfOrder() {
+ List<JobStatus> jobs = new ArrayList<>();
+ JobStatus job1 = createJobStatus("testRemove", createJobInfo(1), 1);
+ JobStatus job2 = createJobStatus("testRemove", createJobInfo(2), 1);
+ JobStatus job3 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 1);
+ JobStatus job4 = createJobStatus("testRemove",
+ createJobInfo(4).setPriority(JobInfo.PRIORITY_MIN), 1);
+ JobStatus job5 = createJobStatus("testRemove", createJobInfo(5).setExpedited(true), 1);
+
+ // Enqueue order (by ID): 4, 5, 3, {1,2 -- at the same time}
+ job1.enqueueTime = 3;
+ job2.enqueueTime = 3;
+ job3.enqueueTime = 4;
+ job4.enqueueTime = 1;
+ job5.enqueueTime = 2;
+
+ // 1 & 2 have the same enqueue time (could happen at boot), so ordering won't be consistent
+ // between the two
+ // Result job order should be (by ID): 5, 3, {1,2}, {1,2}, 4
+
+ // Intended removal order (by ID): 5, 3, 2, 1, 4
+ jobs.add(job5);
+ jobs.add(job3);
+ jobs.add(job2);
+ jobs.add(job1);
+ jobs.add(job4);
+
+ PendingJobQueue jobQueue = new PendingJobQueue();
+ jobQueue.addAll(jobs);
+
+ ArraySet<JobStatus> removed = new ArraySet<>();
+ JobStatus job;
+ while ((job = jobQueue.next()) != null) {
+ Log.d(TAG, testJobToString(job));
+ }
+ for (int i = 0; i < jobs.size(); ++i) {
+ jobQueue.remove(jobs.get(i));
+ removed.add(jobs.get(i));
+
+ assertEquals(jobs.size() - i - 1, jobQueue.size());
+
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertFalse("Queue retained a removed job " + testJobToString(job),
+ removed.contains(job));
+ }
+ }
+ assertNull(jobQueue.next());
+
+ // Intended removal order (by ID): 3, 1, 2, 5, 4
+ jobs.clear();
+ jobs.add(job3);
+ jobs.add(job1);
+ jobs.add(job5);
+ jobs.add(job2);
+ jobs.add(job4);
+
+ jobQueue.addAll(jobs);
+
+ removed.clear();
+ for (int i = 0; i < jobs.size(); ++i) {
+ jobQueue.remove(jobs.get(i));
+ removed.add(jobs.get(i));
+
+ assertEquals(jobs.size() - i - 1, jobQueue.size());
+
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertFalse("Queue retained a removed job " + testJobToString(job),
+ removed.contains(job));
+ }
+ }
+ assertNull(jobQueue.next());
+
+ // Intended removal order (by ID): 3, 2, 1, 4, 5
+ jobs.clear();
+ jobs.add(job3);
+ jobs.add(job2);
+ jobs.add(job1);
+ jobs.add(job4);
+ jobs.add(job5);
+
+ jobQueue.addAll(jobs);
+
+ removed.clear();
+ for (int i = 0; i < jobs.size(); ++i) {
+ jobQueue.remove(jobs.get(i));
+ removed.add(jobs.get(i));
+
+ assertEquals(jobs.size() - i - 1, jobQueue.size());
+
+ jobQueue.resetIterator();
+ while ((job = jobQueue.next()) != null) {
+ assertFalse("Queue retained a removed job " + testJobToString(job),
+ removed.contains(job));
+ }
}
assertNull(jobQueue.next());
}
@@ -224,7 +387,7 @@
}
final JobStatus[] expectedOptimizedOrder = new JobStatus[]{
- eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13};
+ eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13};
idx = 0;
jobQueue.setOptimizeIteration(true);
jobQueue.resetIterator();
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 6c7f872..0f2fe44 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -991,19 +991,20 @@
@Test
public void testUidForeground() throws Exception {
// push all uids into background
- callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, 0);
- callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_SERVICE, 0);
+ long procStateSeq = 0;
+ callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
+ callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
assertFalse(mService.isUidForeground(UID_A));
assertFalse(mService.isUidForeground(UID_B));
// push one of the uids into foreground
- callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, 0);
+ callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP, procStateSeq++);
assertTrue(mService.isUidForeground(UID_A));
assertFalse(mService.isUidForeground(UID_B));
// and swap another uid into foreground
- callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, 0);
- callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_TOP, 0);
+ callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq++);
+ callOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_TOP, procStateSeq++);
assertFalse(mService.isUidForeground(UID_A));
assertTrue(mService.isUidForeground(UID_B));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index a8282600..02009b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -170,7 +170,7 @@
}
@Test
- public void testCanContainActivities() {
+ public void testCanActivityBeLaunched() {
ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
mSupervisor, mock(ActivityStartInterceptor.class));
final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
@@ -204,6 +204,13 @@
ArraySet<Integer> mRunningUids = new ArraySet<>();
@Override
+ public boolean canActivityBeLaunched(@NonNull ActivityInfo activity,
+ @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
+ boolean isNewTask) {
+ return false;
+ }
+
+ @Override
public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
@WindowConfiguration.WindowingMode int windowingMode) {
final int activityCount = activities.size();
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index d3a5896..97d5215 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -232,23 +232,26 @@
public void updateUiTranslationStateLocked(@UiTranslationState int state,
TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId, UiTranslationSpec uiTranslationSpec) {
- // Get top activity for a given task id
- final ActivityTokens taskTopActivityTokens =
- mActivityTaskManagerInternal.getTopActivityForTask(taskId);
- if (taskTopActivityTokens == null
- || taskTopActivityTokens.getShareableActivityToken() != token) {
- Slog.w(TAG, "Unknown activity or it was finished to query for update translation "
- + "state for token=" + token + " taskId=" + taskId + " for state= " + state);
+ // If the app starts a new Activity in the same task then the finish or pause API
+ // is called, the operation doesn't work if we only check task top Activity. The top
+ // Activity is the new Activity, the original Activity is paused in the same task.
+ // To make sure the operation still work, we use the token to find the target Activity in
+ // this task, not the top Activity only.
+ ActivityTokens candidateActivityTokens =
+ mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token);
+ if (candidateActivityTokens == null) {
+ Slog.w(TAG, "Unknown activity or it was finished to query for update "
+ + "translation state for token=" + token + " taskId=" + taskId + " for "
+ + "state= " + state);
return;
}
- mLastActivityTokens = new WeakReference<>(taskTopActivityTokens);
+ mLastActivityTokens = new WeakReference<>(candidateActivityTokens);
if (state == STATE_UI_TRANSLATION_FINISHED) {
mWaitingFinishedCallbackActivities.add(token);
}
-
- IBinder activityToken = taskTopActivityTokens.getActivityToken();
+ IBinder activityToken = candidateActivityTokens.getActivityToken();
try {
- taskTopActivityTokens.getApplicationThread().updateUiTranslationState(
+ candidateActivityTokens.getApplicationThread().updateUiTranslationState(
activityToken, state, sourceSpec, targetSpec,
viewIds, uiTranslationSpec);
} catch (RemoteException e) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 01a9652..6f89bb2 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1453,7 +1453,7 @@
@NonNull String packageName, int eventType) {
synchronized (mLock) {
if (!mUserUnlockedStates.contains(userId)) {
- Slog.w(TAG, "Failed to query earliset package events for locked user " + userId);
+ Slog.w(TAG, "Failed to query earliest package events for locked user " + userId);
return null;
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 79f5808..c609add 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -48,6 +48,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
@@ -104,6 +105,23 @@
void onNewUpdate(int mUserId);
}
+ private static final class CachedEarlyEvents {
+ public long searchBeginTime;
+
+ public long eventTime;
+
+ @Nullable
+ public List<UsageEvents.Event> events;
+ }
+
+ /**
+ * Mapping of {@link UsageEvents.Event} event value to packageName-cached early usage event.
+ * This is used to reduce how much we need to interact with the underlying database to get the
+ * earliest event for a specific package.
+ */
+ private final SparseArrayMap<String, CachedEarlyEvents> mCachedEarlyEvents =
+ new SparseArrayMap<>();
+
UserUsageStatsService(Context context, int userId, File usageStatsDir,
StatsUpdatedListener listener) {
mContext = context;
@@ -177,9 +195,14 @@
void userStopped() {
// Flush events to disk immediately to guarantee persistence.
persistActiveStats();
+ mCachedEarlyEvents.clear();
}
int onPackageRemoved(String packageName, long timeRemoved) {
+ for (int i = mCachedEarlyEvents.numMaps() - 1; i >= 0; --i) {
+ final int eventType = mCachedEarlyEvents.keyAt(i);
+ mCachedEarlyEvents.delete(eventType, packageName);
+ }
return mDatabase.onPackageRemoved(packageName, timeRemoved);
}
@@ -240,6 +263,7 @@
}
private void onTimeChanged(long oldTime, long newTime) {
+ mCachedEarlyEvents.clear();
persistActiveStats();
mDatabase.onTimeChanged(newTime - oldTime);
loadActiveStats(newTime);
@@ -675,14 +699,56 @@
* for the package as well as the earliest event of {@code eventType} seen for the package.
*/
@Nullable
- UsageEvents queryEarliestEventsForPackage(final long beginTime, final long endTime,
+ UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime,
@NonNull final String packageName, final int eventType) {
- if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+ final long currentTime = checkAndGetTimeLocked();
+ if (!validRange(currentTime, beginTime, endTime)) {
return null;
}
+
+ CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName);
+ if (cachedEvents != null) {
+ // We can use this cached event if the previous search time was the exact same
+ // or earlier AND the event we previously found was at this current time or
+ // afterwards. Since no new events will be added before the cached event,
+ // redoing the search will yield the same event.
+ if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) {
+ final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size();
+ if ((numEvents == 0
+ || cachedEvents.events.get(numEvents - 1).getEventType() != eventType)
+ && cachedEvents.eventTime < endTime) {
+ // We didn't find a match in the earlier range but this new request is allowing
+ // us to look at events after the previous request's end time, so we may find
+ // something new.
+ beginTime = Math.min(currentTime, cachedEvents.eventTime);
+ // Leave the cachedEvents's searchBeginTime as the earlier begin time to
+ // cache/show that we searched the entire range (the union of the two queries):
+ // [previous query's begin time, current query's end time].
+ } else if (cachedEvents.eventTime <= endTime) {
+ if (cachedEvents.events == null) {
+ return null;
+ }
+ return new UsageEvents(cachedEvents.events, new String[]{packageName}, false);
+ } else {
+ // Any event we previously found is after the end of this query's range, but
+ // this query starts at the same time (or after) the previous query's begin time
+ // so there is no event to return.
+ return null;
+ }
+ } else {
+ // The previous query isn't helpful in any way for this query. Reset the event data.
+ cachedEvents.searchBeginTime = beginTime;
+ }
+ } else {
+ cachedEvents = new CachedEarlyEvents();
+ cachedEvents.searchBeginTime = beginTime;
+ mCachedEarlyEvents.add(eventType, packageName, cachedEvents);
+ }
+
+ final long finalBeginTime = beginTime;
final List<Event> results = queryStats(INTERVAL_DAILY,
beginTime, endTime, (stats, mutable, accumulatedResult) -> {
- final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
+ final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime);
final int size = stats.events.size();
for (int i = startIndex; i < size; i++) {
final Event event = stats.events.get(i);
@@ -706,9 +772,15 @@
});
if (results == null || results.isEmpty()) {
+ // There won't be any new events added earlier than endTime, so we can use endTime to
+ // avoid querying for events earlier than it.
+ cachedEvents.eventTime = Math.min(currentTime, endTime);
+ cachedEvents.events = null;
return null;
}
+ cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp();
+ cachedEvents.events = results;
return new UsageEvents(results, new String[]{packageName}, false);
}
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 22a6445..bc8c639 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,6 +5,19 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
]
},
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0519873..edf1002 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -311,8 +311,8 @@
callback.sendResult(null);
return;
}
- final ActivityTokens tokens = LocalServices.getService(
- ActivityTaskManagerInternal.class).getTopActivityForTask(taskId);
+ final ActivityTokens tokens = LocalServices.getService(ActivityTaskManagerInternal.class)
+ .getAttachedNonFinishingActivityForTask(taskId, null);
if (tokens == null || tokens.getAssistToken() != assistToken) {
Slog.w(TAG, "Unknown activity to query for direct actions");
callback.sendResult(null);
@@ -336,8 +336,8 @@
resultCallback.sendResult(null);
return;
}
- final ActivityTokens tokens = LocalServices.getService(
- ActivityTaskManagerInternal.class).getTopActivityForTask(taskId);
+ final ActivityTokens tokens = LocalServices.getService(ActivityTaskManagerInternal.class)
+ .getAttachedNonFinishingActivityForTask(taskId, null);
if (tokens == null || tokens.getAssistToken() != assistToken) {
Slog.w(TAG, "Unknown activity to perform a direct action");
resultCallback.sendResult(null);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a2266fb..a6a7c84 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -980,6 +980,12 @@
public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
= "carrier_volte_tty_supported_bool";
+ /** Flag specifying whether VoWIFI TTY is supported.
+ * @hide
+ */
+ public static final String KEY_CARRIER_VOWIFI_TTY_SUPPORTED_BOOL =
+ "carrier_vowifi_tty_supported_bool";
+
/**
* Flag specifying whether IMS service can be turned off. If false then the service will not be
* turned-off completely, but individual features can be disabled.
@@ -1924,6 +1930,13 @@
"show_4g_for_lte_data_icon_bool";
/**
+ * Boolean indicating if default data account should show 4G LTE or 4G icon.
+ * @hide
+ */
+ public static final String KEY_SHOW_4GLTE_FOR_LTE_DATA_ICON_BOOL =
+ "show_4glte_for_lte_data_icon_bool";
+
+ /**
* Boolean indicating if default data account should show 4G icon when in 3G.
*/
public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL =
@@ -8574,6 +8587,7 @@
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_VOWIFI_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
@@ -8907,6 +8921,7 @@
sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, false);
+ sDefaults.putBoolean(KEY_SHOW_4GLTE_FOR_LTE_DATA_ICON_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL, false);
sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
new file mode 100644
index 0000000..4e360f9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class NotificationAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ fun postNotification(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ val button = device.wait(
+ Until.findObject(By.res(getPackage(), "post_notification")),
+ FIND_TIMEOUT)
+
+ require(button != null) {
+ "Post notification button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)"
+ }
+ button.click()
+
+ device.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
+ ?: error("Flicker Notification not found")
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
new file mode 100644
index 0000000..bd2e575
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+class ShowWhenLockedAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
new file mode 100644
index 0000000..8daf4ca
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+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.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@Postsubmit
+open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter)
+ : OpenAppFromNotificationCold(testSpec) {
+
+ override val openingNotificationsFromLockScreen = true
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ // Needs to run at start of transition,
+ // so before the transition defined in super.transition
+ transitions {
+ device.wakeUp()
+ }
+
+ super.transition(this)
+
+ // Needs to run at the end of the setup, so after the setup defined in super.transition
+ setup {
+ eachRun {
+ device.sleep()
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 3)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
new file mode 100644
index 0000000..8eb182a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+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.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from a notification from the lock screen.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@Postsubmit
+open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter)
+ : OpenAppFromNotificationWarm(testSpec) {
+
+ override val openingNotificationsFromLockScreen = true
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ // Needs to run at start of transition,
+ // so before the transition defined in super.transition
+ transitions {
+ device.wakeUp()
+ }
+
+ super.transition(this)
+
+ // Needs to run at the end of the setup, so after the setup defined in super.transition
+ setup {
+ eachRun {
+ device.sleep()
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that we start of with no top windows and then [testApp] becomes the first and
+ * only top window of the transition, with snapshot or splash screen windows optionally showing
+ * first.
+ */
+ @Test
+ @Postsubmit
+ open fun appWindowBecomesFirstAndOnlyTopWindow() {
+ testSpec.assertWm {
+ this.hasNoVisibleAppWindow()
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the screen is locked.
+ */
+ @Test
+ @Postsubmit
+ fun screenLockedStart() {
+ testSpec.assertLayersStart {
+ isEmpty()
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 3)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
new file mode 100644
index 0000000..28a914b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+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.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen when there is an app
+ * overlaid on the lock screen.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@Postsubmit
+class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter)
+ : OpenAppFromLockNotificationCold(testSpec) {
+ private val showWhenLockedApp: ShowWhenLockedAppHelper =
+ ShowWhenLockedAppHelper(instrumentation)
+
+ // Although we are technically still locked here, the overlay app means we should open the
+ // notification shade as if we were unlocked.
+ override val openingNotificationsFromLockScreen = false
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+
+ // Launch an activity that is shown when the device is locked
+ showWhenLockedApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(showWhenLockedApp.component)
+
+ device.sleep()
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ }
+ }
+
+ teardown {
+ test {
+ showWhenLockedApp.exit(wmHelper)
+ }
+ }
+ }
+
+ @Test
+ @Postsubmit
+ fun showWhenLockedAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.hasNoVisibleAppWindow()
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowOnTop(showWhenLockedApp.component)
+ }
+ }
+
+ @Test
+ @Postsubmit
+ fun showWhenLockedAppLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(showWhenLockedApp.component)
+ .then()
+ .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(showWhenLockedApp.component)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 3)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
new file mode 100644
index 0000000..ee018ec
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+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.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@Postsubmit
+open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter)
+ : OpenAppFromNotificationWarm(testSpec) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+
+ setup {
+ eachRun {
+ // Close the app that posted the notification to trigger a cold start next time
+ // it is open - can't just kill it because that would remove the notification.
+ taplInstrumentation.goHome()
+ taplInstrumentation.workspace.switchToOverview()
+ taplInstrumentation.overview.dismissAllTasks()
+ }
+ }
+ }
+
+ @Test
+ @Postsubmit
+ override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+
+ @Test
+ @Postsubmit
+ override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 3)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
new file mode 100644
index 0000000..74f1fd7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@Postsubmit
+open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
+ : OpenAppTransition(testSpec) {
+ protected val taplInstrumentation = LauncherInstrumentation()
+
+ override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+ open val openingNotificationsFromLockScreen = false
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(testSpec.startRotation)
+ }
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp.component)
+ testApp.postNotification(device, wmHelper)
+ device.pressHome()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+
+ transitions {
+ var startY = 10
+ var endY = 3 * device.displayHeight / 4
+ var steps = 25
+ if (openingNotificationsFromLockScreen) {
+ val wm = instrumentation.context.getSystemService(WindowManager::class.java)
+ val metricInsets = wm.currentWindowMetrics.windowInsets
+ val insets = metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.statusBars()
+ or WindowInsets.Type.displayCutout())
+
+ startY = insets.top + 100
+ endY = device.displayHeight / 2
+ steps = 4
+ }
+
+ // Swipe down to show the notification shade
+ val x = device.displayWidth / 2
+ device.swipe(x, startY, x, endY, steps)
+ device.waitForIdle(2000)
+ instrumentation.uiAutomation.syncInputTransactions()
+
+ // Launch the activity by clicking the notification
+ val notification = device.wait(Until.findObject(
+ By.text("Flicker Test Notification")), 2000L)
+ notification?.click() ?: error("Notification not found")
+ instrumentation.uiAutomation.syncInputTransactions()
+
+ // Wait for the app to launch
+ wmHelper.waitForFullScreenApp(testApp.component)
+ }
+
+ teardown {
+ test {
+ testApp.exit(wmHelper)
+ }
+ }
+ }
+
+ @Test
+ @Postsubmit
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @Test
+ @Postsubmit
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+
+ @Test
+ @Postsubmit
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
+ @Test
+ @Postsubmit
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ @Postsubmit
+ override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+
+ @Test
+ @Postsubmit
+ override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+
+ @Test
+ @Postsubmit
+ fun notificationAppWindowVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ this.isAppWindowVisible(testApp.component)
+ }
+ }
+
+ @Test
+ @Postsubmit
+ fun notificationAppWindowOnTopAtEnd() {
+ testSpec.assertWmEnd {
+ this.isAppWindowOnTop(testApp.component)
+ }
+ }
+
+ @Test
+ @Postsubmit
+ fun notificationAppLayerVisibleAtEnd() {
+ testSpec.assertLayersEnd {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 3)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 7f513b2..2842232 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -130,5 +130,27 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ShowWhenLockedActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:label="ShowWhenLockedActivity"
+ android:showWhenLocked="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".NotificationActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml
new file mode 100644
index 0000000..09bd44c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_notification.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
+</vector>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml
new file mode 100644
index 0000000..7807200
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/notification_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_orange_light">
+ <Button
+ android:id="@+id/post_notification"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Post Notification" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 18c95cf..e080709 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -72,4 +72,14 @@
public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
+
+ public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
+ public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+
+ public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
+ public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NotificationActivity");
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
new file mode 100644
index 0000000..b31af38
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class NotificationActivity extends Activity {
+ private static final String CHANNEL_ID = "notification_channel";
+ private static final int NOTIFICATION_ID = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.notification_button);
+
+ Button button = findViewById(R.id.post_notification);
+ button.setOnClickListener(v -> postNotification());
+
+ createNotificationChannel();
+ }
+
+ private void postNotification() {
+ Intent resultIntent = new Intent(this, NotificationActivity.class);
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addNextIntentWithParentStack(resultIntent);
+ PendingIntent resultPendingIntent =
+ stackBuilder.getPendingIntent(0,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle("Flicker Test Notification")
+ .setContentText("Flicker Test Notification")
+ // Set the intent that will fire when the user taps the notification
+ .setContentIntent(resultPendingIntent)
+ .setAutoCancel(true);
+
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.notify(NOTIFICATION_ID, builder.build());
+ }
+
+ private void createNotificationChannel() {
+ CharSequence name = "channel_name";
+ String description = "channel_description";
+ int importance = NotificationManager.IMPORTANCE_HIGH;
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
+ channel.setDescription(description);
+ // Register the channel with the system; you can't change the importance
+ // or other notification behaviors after this
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java
new file mode 100644
index 0000000..6f94b74
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ShowWhenLockedActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ShowWhenLockedActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.activity_simple);
+ }
+}
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index 5fb260f..9ac4135 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -31,4 +31,10 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.inputmethod.stresstest" />
</test>
+
+ <!-- Collect the files in the dump directory for debugging -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/InputMethodStressTest/" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index f0f78740..c84c2bc 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -48,6 +48,10 @@
@Rule
public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+ @Rule
+ public ScreenCaptureRule mScreenCaptureRule =
+ new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+
@Test
public void autoShow() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 4be07fb..1c957d4d 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -55,6 +55,10 @@
@Rule
public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+ @Rule
+ public ScreenCaptureRule mScreenCaptureRule =
+ new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+
@Test
public void test() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
index 356c470..29c52cf 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
@@ -48,6 +48,7 @@
import org.junit.runner.RunWith;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
@RootPermissionTest
@RunWith(AndroidJUnit4.class)
@@ -75,6 +76,10 @@
@Rule
public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+ @Rule
+ public ScreenCaptureRule mScreenCaptureRule =
+ new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+
private Context mContext;
private NotificationManager mNotificationManager;
private UiDevice mUiDevice;
@@ -95,7 +100,9 @@
public void testDirectReply() {
postMessagingNotification();
mUiDevice.openNotification();
- mUiDevice.wait(Until.findObject(By.text(REPLY_ACTION_LABEL)), TIMEOUT).click();
+ // The text can be shown as-is, or all-caps, depending on the system.
+ Pattern actionLabelPattern = Pattern.compile(REPLY_ACTION_LABEL, Pattern.CASE_INSENSITIVE);
+ mUiDevice.wait(Until.findObject(By.text(actionLabelPattern)), TIMEOUT).click();
// Verify that IME is visible.
assertThat(mUiDevice.wait(Until.findObject(By.pkg(getImePackage(mContext))), TIMEOUT))
.isNotNull();
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java
new file mode 100644
index 0000000..4e4ef2e
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenCaptureRule.java
@@ -0,0 +1,69 @@
+/*
+ * 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.inputmethod.stresstest;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Takes a screenshot when the test fails.
+ *
+ * <p>Use {@link com.android.tradefed.device.metric.FilePullerLogCollector} to collect screenshots
+ * taken.
+ * For example, in AndroidTest.xml:
+ * <code>
+ * <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ * <option name="directory-keys" value="/sdcard/MyTest/" />
+ * <option name="collect-on-run-ended-only" value="true" />
+ * </metrics_collector>
+ * </code>
+ * in MyTest.java:
+ * <code>
+ * @Rule
+ * public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/MyTest");
+ * </code>
+ */
+public class ScreenCaptureRule extends TestWatcher {
+
+ private static final String TAG = "ScreenCaptureRule";
+
+ private final String mOutDir;
+
+ public ScreenCaptureRule(String outDir) {
+ mOutDir = outDir;
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+ super.failed(e, description);
+ String time = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
+ String fileName = "screenshot-" + time + ".png";
+ capture(fileName);
+ }
+
+ /** Take a screenshot. */
+ public void capture(String fileName) {
+ SystemUtil.runCommandAndPrintOnLogcat(TAG, String.format("mkdir -p %s", mOutDir));
+ SystemUtil.runCommandAndPrintOnLogcat(TAG,
+ String.format("screencap %s/%s", mOutDir, fileName));
+ }
+}