Merge "Add the slider widget to SPA and create samples in codelab slider page."
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index 051216f..e02ba77 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -1,4 +1,7 @@
# Bug component: 136048
+hcutts@google.com
+joseprio@google.com
michaelwr@google.com
prabirmsp@google.com
svv@google.com
+vdevmurari@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 65b2511..f8aa7e9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -17,17 +17,12 @@
tests/
tools/
bpfmt = -d
+
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
-
hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
-
-owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
-
-shell_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^packages/Shell/"
diff --git a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
index 89a5219..c908d6a 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidManifest.xml
@@ -18,7 +18,7 @@
<application android:label="SurfaceFlingerPerfTests">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.perftests.utils.SurfaceFlingerTestActivity"
+ <activity android:name="android.surfaceflinger.SurfaceFlingerTestActivity"
android:exported="true">
<intent-filter>
diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
index b8bd8b4..0f3a068 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
@@ -52,12 +52,20 @@
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
<option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+ <!-- SimpleperfListener related arguments -->
+ <option name="instrumentation-arg" key="report" value="true" />
+ <option name="instrumentation-arg" key="arguments" value="""" />
+ <option name="instrumentation-arg" key="events_to_record" value="instructions,cpu-cycles,raw-l3d-cache-refill,sched:sched_waking" />
+ <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger" />
+ <option name="instrumentation-arg" key="symbols_to_report" value=""android::SurfaceFlinger::commit(long, long, long)"" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/local/tmp/SurfaceFlingerPerfTests" />
<!-- Needed for pulling the collected trace config on to the host -->
<option name="pull-pattern-keys" value="perfetto_file_path" />
+ <option name="pull-pattern-keys" value="simpleperf_file_path" />
</metrics_collector>
</configuration>
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java
new file mode 100644
index 0000000..52fb8a6
--- /dev/null
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/BufferFlinger.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.surfaceflinger;
+
+import android.annotation.ColorInt;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.hardware.SyncFence;
+import android.view.SurfaceControl;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * Allocates n amount of buffers to a SurfaceControl using a Queue implementation. Executes a
+ * releaseCallback so a buffer can be safely re-used.
+ *
+ * @hide
+ */
+public class BufferFlinger {
+ ArrayBlockingQueue<GraphicBuffer> mBufferQ;
+
+ public BufferFlinger(int numOfBuffers, @ColorInt int color) {
+ mBufferQ = new ArrayBlockingQueue<>(numOfBuffers);
+
+ while (numOfBuffers > 0) {
+ GraphicBuffer buffer = GraphicBuffer.create(500, 500,
+ PixelFormat.RGBA_8888,
+ GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+ | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+ Canvas canvas = buffer.lockCanvas();
+ canvas.drawColor(color);
+ buffer.unlockCanvasAndPost(canvas);
+
+ mBufferQ.add(buffer);
+ numOfBuffers--;
+ }
+ }
+
+ public void addBuffer(SurfaceControl.Transaction t, SurfaceControl surfaceControl)
+ throws InterruptedException {
+ GraphicBuffer buffer = mBufferQ.take();
+ t.setBuffer(surfaceControl,
+ HardwareBuffer.createFromGraphicBuffer(buffer),
+ null,
+ (SyncFence fence) -> {
+ releaseCallback(fence, buffer);
+ });
+ }
+
+ public void releaseCallback(SyncFence fence, GraphicBuffer buffer) {
+ if (fence != null) {
+ fence.awaitForever();
+ }
+ mBufferQ.add(buffer);
+ }
+
+ public void freeBuffers() {
+ for (GraphicBuffer buffer : mBufferQ) {
+ buffer.destroy();
+ }
+ }
+}
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
index 347cb78..f4d0c05 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
@@ -16,38 +16,51 @@
package android.surfaceflinger;
+import android.graphics.Color;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.SurfaceFlingerTestActivity;
+import android.view.SurfaceControl;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-
@LargeTest
@RunWith(AndroidJUnit4.class)
public class SurfaceFlingerPerfTest {
protected ActivityScenarioRule<SurfaceFlingerTestActivity> mActivityRule =
new ActivityScenarioRule<>(SurfaceFlingerTestActivity.class);
protected PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ private SurfaceFlingerTestActivity mActivity;
+ static final int BUFFER_COUNT = 2;
@Rule
public final RuleChain mAllRules = RuleChain
.outerRule(mPerfStatusReporter)
.around(mActivityRule);
-
+ @Before
+ public void setup() {
+ mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+ }
@Test
- public void helloWorld() throws Exception {
+ public void submitSingleBuffer() throws Exception {
+ SurfaceControl sc = mActivity.getChildSurfaceControl();
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ BufferFlinger bufferflinger = new BufferFlinger(BUFFER_COUNT, Color.GREEN);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- // Do Something
- }
+ t.show(sc);
+ while (state.keepRunning()) {
+ bufferflinger.addBuffer(t, sc);
+ t.apply();
+ }
+ bufferflinger.freeBuffers();
}
}
+
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
new file mode 100644
index 0000000..a9b2a31
--- /dev/null
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerTestActivity.java
@@ -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 android.surfaceflinger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class SurfaceFlingerTestActivity extends Activity {
+ public TestSurfaceView mTestSurfaceView;
+ SurfaceControl mSurfaceControl;
+ CountDownLatch mIsReady = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mTestSurfaceView = new TestSurfaceView(this);
+ setContentView(mTestSurfaceView);
+ }
+
+ public SurfaceControl getChildSurfaceControl() throws InterruptedException {
+ return mTestSurfaceView.getChildSurfaceControlHelper();
+ }
+
+ public class TestSurfaceView extends SurfaceView {
+ public TestSurfaceView(Context context) {
+ super(context);
+ SurfaceHolder holder = getHolder();
+ holder.addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mIsReady.countDown();
+ }
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {}
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+ });
+ }
+
+ public SurfaceControl getChildSurfaceControlHelper() throws InterruptedException {
+ mIsReady.await();
+ SurfaceHolder holder = getHolder();
+
+ // check to see if surface is valid
+ if (holder.getSurface().isValid()) {
+ mSurfaceControl = getSurfaceControl();
+ }
+ return new SurfaceControl.Builder()
+ .setName("ChildSurfaceControl")
+ .setParent(mSurfaceControl)
+ .build();
+ }
+ }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java
deleted file mode 100644
index 511ebbe..0000000
--- a/apct-tests/perftests/utils/src/android/perftests/utils/SurfaceFlingerTestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 android.perftests.utils;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.SurfaceView;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-
-/**
- * A simple activity used for testing, e.g. performance of activity switching, or as a base
- * container of testing view.
- */
-public class SurfaceFlingerTestActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- final LinearLayout layout = new LinearLayout(this);
- layout.setOrientation(LinearLayout.VERTICAL);
-
- final SurfaceView surfaceview = new SurfaceView(this);
- layout.addView(surfaceview);
- setContentView(layout);
-
- }
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d8e25b6..c0a9e67 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -3078,15 +3078,15 @@
pw.decreaseIndent();
pw.println();
} else {
- if (mAppStateTracker != null) {
- mAppStateTracker.dump(pw);
- pw.println();
- }
-
pw.println("App Standby Parole: " + mAppStandbyParole);
pw.println();
}
+ if (mAppStateTracker != null) {
+ mAppStateTracker.dump(pw);
+ pw.println();
+ }
+
final long nowELAPSED = mInjector.getElapsedRealtime();
final long nowUPTIME = SystemClock.uptimeMillis();
final long nowRTC = mInjector.getCurrentTimeMillis();
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 73508c8..794362b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1556,7 +1556,10 @@
// Create the controllers.
mControllers = new ArrayList<StateController>();
- final FlexibilityController flexibilityController = new FlexibilityController(this);
+ mPrefetchController = new PrefetchController(this);
+ mControllers.add(mPrefetchController);
+ final FlexibilityController flexibilityController =
+ new FlexibilityController(this, mPrefetchController);
mControllers.add(flexibilityController);
final ConnectivityController connectivityController =
new ConnectivityController(this, flexibilityController);
@@ -1575,8 +1578,6 @@
mControllers.add(new ContentObserverController(this));
mDeviceIdleJobsController = new DeviceIdleJobsController(this);
mControllers.add(mDeviceIdleJobsController);
- mPrefetchController = new PrefetchController(this);
- mControllers.add(mPrefetchController);
mQuotaController =
new QuotaController(this, backgroundJobsController, connectivityController);
mControllers.add(mQuotaController);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index f4ee0ae..e69cbed 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -23,6 +23,7 @@
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import android.annotation.ElapsedRealtimeLong;
@@ -36,6 +37,7 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,8 +52,6 @@
/**
* Controller that tracks the number of flexible constraints being actively satisfied.
* Drops constraint for TOP apps and lowers number of required constraints with time.
- *
- * TODO(b/238887951): handle prefetch
*/
public final class FlexibilityController extends StateController {
private static final String TAG = "JobScheduler.Flexibility";
@@ -78,6 +78,8 @@
@VisibleForTesting
static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
+ private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
+
/** Hard cutoff to remove flexible constraints. */
private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@@ -85,7 +87,8 @@
* The default deadline that all flexible constraints should be dropped by if a job lacks
* a deadline.
*/
- private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
+ @VisibleForTesting
+ static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
/**
* Keeps track of what flexible constraints are satisfied at the moment.
@@ -102,6 +105,10 @@
final FlexibilityTracker mFlexibilityTracker;
private final FcConstants mFcConstants;
+ @VisibleForTesting
+ final PrefetchController mPrefetchController;
+
+ @GuardedBy("mLock")
private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
@@ -112,12 +119,58 @@
*/
private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
- public FlexibilityController(JobSchedulerService service) {
+ /**
+ * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
+ * the last time the app was used and the last time the launch time was updated.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>();
+
+ @VisibleForTesting
+ final PrefetchController.PrefetchChangedListener mPrefetchChangedListener =
+ new PrefetchController.PrefetchChangedListener() {
+ @Override
+ public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
+ String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+ synchronized (mLock) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long prefetchThreshold =
+ mPrefetchController.getLaunchTimeThresholdMs();
+ boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime
+ - prefetchThreshold < nowElapsed;
+ boolean jobIsInPrefetchWindow = newEstimatedLaunchTime
+ - prefetchThreshold < nowElapsed;
+ if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) {
+ // If the job was in the window previously then changing the start
+ // of the lifecycle to the current moment without a large change in the
+ // end would squeeze the window too tight fail to drop constraints.
+ mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed,
+ mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L)));
+ }
+ for (int i = 0; i < jobs.size(); i++) {
+ JobStatus js = jobs.valueAt(i);
+ if (!js.hasFlexibilityConstraint()) {
+ continue;
+ }
+ mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ }
+ }
+ }
+ };
+
+ public FlexibilityController(
+ JobSchedulerService service, PrefetchController prefetchController) {
super(service);
mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
mFcConstants = new FcConstants();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
mContext, JobSchedulerBackgroundThread.get().getLooper());
+ mPrefetchController = prefetchController;
+ if (mFlexibilityEnabled) {
+ mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
+ }
}
/**
@@ -131,7 +184,7 @@
js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
final long nowElapsed = sElapsedRealtimeClock.millis();
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
- mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js));
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
}
}
@@ -144,6 +197,19 @@
}
}
+ @Override
+ @GuardedBy("mLock")
+ public void onAppRemovedLocked(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mPrefetchLifeCycleStart.delete(userId, packageName);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void onUserRemovedLocked(int userId) {
+ mPrefetchLifeCycleStart.delete(userId);
+ }
+
/** Checks if the flexibility constraint is actively satisfied for a given job. */
@GuardedBy("mLock")
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
@@ -165,6 +231,7 @@
* Sets the controller's constraint to a given state.
* Changes flexibility constraint satisfaction for affected jobs.
*/
+ @VisibleForTesting
void setConstraintSatisfied(int constraint, boolean state) {
synchronized (mLock) {
final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -187,21 +254,20 @@
// of satisfied system-wide constraints and iterate to the max number of potentially
// satisfied constraints, determined by how many job-specific constraints exist.
for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
- final ArraySet<JobStatus> jobs = mFlexibilityTracker
+ final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
.getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
- if (jobs == null) {
+ if (jobsByNumConstraints == null) {
// If there are no more jobs to iterate through we can just return.
return;
}
- for (int i = 0; i < jobs.size(); i++) {
- JobStatus js = jobs.valueAt(i);
+ for (int i = 0; i < jobsByNumConstraints.size(); i++) {
+ JobStatus js = jobsByNumConstraints.valueAt(i);
js.setFlexibilityConstraintSatisfied(
nowElapsed, isFlexibilitySatisfiedLocked(js));
}
}
-
}
}
@@ -211,15 +277,77 @@
return (mSatisfiedFlexibleConstraints & constraint) != 0;
}
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ long getLifeCycleBeginningElapsedLocked(JobStatus js) {
+ if (js.getJob().isPrefetch()) {
+ final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime());
+ final long estimatedLaunchTime =
+ mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+ long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
+ js.getSourceUserId(), js.getSourcePackageName(), 0L);
+ if (estimatedLaunchTime != Long.MAX_VALUE) {
+ prefetchWindowStart = Math.max(prefetchWindowStart,
+ estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs());
+ }
+ return Math.max(prefetchWindowStart, earliestRuntime);
+ }
+ return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
+ ? js.enqueueTime : js.getEarliestRunTime();
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+ if (js.getJob().isPrefetch()) {
+ final long estimatedLaunchTime =
+ mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
+ // Prefetch jobs aren't supposed to have deadlines after T.
+ // But some legacy apps might still schedule them with deadlines.
+ if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) {
+ // If there is a deadline, the earliest time is the end of the lifecycle.
+ return Math.min(
+ estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+ js.getLatestRunTimeElapsed());
+ }
+ if (estimatedLaunchTime != Long.MAX_VALUE) {
+ return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
+ }
+ // There is no deadline and no estimated launch time.
+ return NO_LIFECYCLE_END;
+ }
+ return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
+ ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
+ : js.getLatestRunTimeElapsed();
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ int getCurPercentOfLifecycleLocked(JobStatus js) {
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (latest == NO_LIFECYCLE_END || earliest > nowElapsed) {
+ return 0;
+ }
+ if (nowElapsed > latest || latest == earliest) {
+ return 100;
+ }
+ final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest));
+ return percentInTime;
+ }
+
/** The elapsed time that marks when the next constraint should be dropped. */
@VisibleForTesting
@ElapsedRealtimeLong
- long getNextConstraintDropTimeElapsed(JobStatus js) {
- final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
- ? js.enqueueTime : js.getEarliestRunTime();
- final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
- ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
- : js.getLatestRunTimeElapsed();
+ @GuardedBy("mLock")
+ long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ if (latest == NO_LIFECYCLE_END
+ || js.getNumDroppedFlexibleConstraints() == PERCENT_TO_DROP_CONSTRAINTS.length) {
+ return NO_LIFECYCLE_END;
+ }
final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
final long percentInTime = ((latest - earliest) * percent) / 100;
return earliest + percentInTime;
@@ -233,10 +361,28 @@
}
final long nowElapsed = sElapsedRealtimeClock.millis();
List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
+ boolean hasPrefetch = false;
for (int i = 0; i < jobsByUid.size(); i++) {
JobStatus js = jobsByUid.get(i);
if (js.hasFlexibilityConstraint()) {
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+ hasPrefetch |= js.getJob().isPrefetch();
+ }
+ }
+
+ // Prefetch jobs can't run when the app is TOP, so it should not be included in their
+ // lifecycle, and marks the beginning of a new lifecycle.
+ if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) {
+ final int userId = UserHandle.getUserId(uid);
+ final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid);
+ if (pkgs == null) {
+ return;
+ }
+ for (int i = 0; i < pkgs.size(); i++) {
+ String pkg = pkgs.valueAt(i);
+ mPrefetchLifeCycleStart.add(userId, pkg,
+ Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L),
+ nowElapsed));
}
}
}
@@ -281,7 +427,7 @@
FlexibilityTracker(int numFlexibleConstraints) {
mTrackedJobs = new ArrayList<>();
- for (int i = 0; i <= numFlexibleConstraints; i++) {
+ for (int i = 0; i < numFlexibleConstraints; i++) {
mTrackedJobs.add(new ArraySet<JobStatus>());
}
}
@@ -312,6 +458,17 @@
mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
}
+ public void resetJobNumDroppedConstraints(JobStatus js) {
+ final int curPercent = getCurPercentOfLifecycleLocked(js);
+ int toDrop = 0;
+ for (int i = 0; i < PERCENT_TO_DROP_CONSTRAINTS.length; i++) {
+ if (curPercent >= PERCENT_TO_DROP_CONSTRAINTS[i]) {
+ toDrop++;
+ }
+ }
+ adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop);
+ }
+
/** Returns all tracked jobs. */
public ArrayList<ArraySet<JobStatus>> getArrayList() {
return mTrackedJobs;
@@ -369,20 +526,39 @@
return js.getSourceUserId() == userId;
}
+ protected void scheduleDropNumConstraintsAlarm(JobStatus js) {
+ long nextTimeElapsed;
+ synchronized (mLock) {
+ nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js);
+ if (nextTimeElapsed == NO_LIFECYCLE_END) {
+ // There is no known or estimated next time to drop a constraint.
+ removeAlarmForKey(js);
+ return;
+ }
+ mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
+ }
+ }
+
@Override
protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
synchronized (mLock) {
- JobStatus js;
+ ArraySet<JobStatus> changedJobs = new ArraySet<>();
for (int i = 0; i < expired.size(); i++) {
- js = expired.valueAt(i);
- long time = getNextConstraintDropTimeElapsed(js);
+ JobStatus js = expired.valueAt(i);
+ boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
+ long time = getNextConstraintDropTimeElapsedLocked(js);
int toDecrease =
js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
- ? -js.getNumRequiredFlexibleConstraints() : -1;
- if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
+ ? -js.getNumRequiredFlexibleConstraints() : -1;
+ if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)
+ && time != NO_LIFECYCLE_END) {
mFlexibilityAlarmQueue.addAlarm(js, time);
}
+ if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
+ changedJobs.add(js);
+ }
}
+ mStateChangedListener.onControllerStateChanged(changedJobs);
}
}
}
@@ -410,6 +586,13 @@
if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
mFlexibilityEnabled = FLEXIBILITY_ENABLED;
mShouldReevaluateConstraints = true;
+ if (mFlexibilityEnabled) {
+ mPrefetchController
+ .registerPrefetchChangedListener(mPrefetchChangedListener);
+ } else {
+ mPrefetchController
+ .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
+ }
}
break;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 52882fe..42e60e4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -574,7 +574,6 @@
if (!isRequestedExpeditedJob()
&& satisfiesMinWindowException
- && !job.isPrefetch()
&& lacksSomeFlexibleConstraints) {
mNumRequiredFlexibleConstraints =
NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 0f385ef..0945b7e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -81,6 +81,8 @@
*/
@GuardedBy("mLock")
private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
+ @GuardedBy("mLock")
+ private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>();
private final ThresholdAlarmListener mThresholdAlarmListener;
/**
@@ -99,6 +101,13 @@
@GuardedBy("mLock")
private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS;
+ /** Called by Prefetch Controller after local cache has been updated */
+ public interface PrefetchChangedListener {
+ /** Callback to inform listeners when estimated launch times change. */
+ void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName,
+ long prevEstimatedLaunchTime, long newEstimatedLaunchTime);
+ }
+
@SuppressWarnings("FieldCanBeLocal")
private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
new EstimatedLaunchTimeChangedListener() {
@@ -291,12 +300,17 @@
// Don't bother caching the value unless the app has scheduled prefetch jobs
// before. This is based on the assumption that if an app has scheduled a
// prefetch job before, then it will probably schedule another one again.
+ final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);
if (!jobs.isEmpty()) {
final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
+ for (int i = 0; i < mPrefetchChangedListeners.size(); i++) {
+ mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs,
+ userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime);
+ }
if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
mStateChangedListener.onControllerStateChanged(jobs);
}
@@ -448,6 +462,18 @@
}
}
+ void registerPrefetchChangedListener(PrefetchChangedListener listener) {
+ synchronized (mLock) {
+ mPrefetchChangedListeners.add(listener);
+ }
+ }
+
+ void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) {
+ synchronized (mLock) {
+ mPrefetchChangedListeners.remove(listener);
+ }
+ }
+
private class PcHandler extends Handler {
PcHandler(Looper looper) {
super(looper);
diff --git a/core/api/current.txt b/core/api/current.txt
index da3746d..ea2ae0c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -275,6 +275,7 @@
public static final class R.attr {
ctor public R.attr();
field public static final int absListViewStyle = 16842858; // 0x101006a
+ field public static final int accessibilityDataPrivate;
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -32263,6 +32264,7 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean isUserUnlocked();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
+ method public boolean isUserVisible();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
method @Deprecated public boolean setRestrictionsChallenge(String);
@@ -49229,6 +49231,10 @@
}
public final class PixelCopy {
+ method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
+ method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
+ method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
+ method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
@@ -49243,10 +49249,21 @@
field public static final int SUCCESS = 0; // 0x0
}
+ public static final class PixelCopy.CopyResult {
+ method @NonNull public android.graphics.Bitmap getBitmap();
+ method public int getStatus();
+ }
+
public static interface PixelCopy.OnPixelCopyFinishedListener {
method public void onPixelCopyFinished(int);
}
+ public static final class PixelCopy.Request {
+ method public void request();
+ method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap);
+ method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect);
+ }
+
public final class PointerIcon implements android.os.Parcelable {
method @NonNull public static android.view.PointerIcon create(@NonNull android.graphics.Bitmap, float, float);
method public int describeContents();
@@ -49939,6 +49956,7 @@
method public void invalidate();
method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
method public void invalidateOutline();
+ method public boolean isAccessibilityDataPrivate();
method public boolean isAccessibilityFocused();
method public boolean isAccessibilityHeading();
method public boolean isActivated();
@@ -50116,6 +50134,7 @@
method public void scrollTo(int, int);
method public void sendAccessibilityEvent(int);
method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
+ method public void setAccessibilityDataPrivate(int);
method public void setAccessibilityDelegate(@Nullable android.view.View.AccessibilityDelegate);
method public void setAccessibilityHeading(boolean);
method public void setAccessibilityLiveRegion(int);
@@ -50296,6 +50315,9 @@
method @CallSuper protected boolean verifyDrawable(@NonNull android.graphics.drawable.Drawable);
method @Deprecated public boolean willNotCacheDrawing();
method public boolean willNotDraw();
+ field public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0; // 0x0
+ field public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 2; // 0x2
+ field public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 1; // 0x1
field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
@@ -51748,9 +51770,11 @@
method public int getSpeechStateChangeTypes();
method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
+ method public boolean isAccessibilityDataPrivate();
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
+ method public void setAccessibilityDataPrivate(boolean);
method public void setAction(int);
method public void setContentChangeTypes(int);
method public void setEventTime(long);
@@ -51841,6 +51865,7 @@
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
+ method public boolean isRequestFromAccessibilityTool();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 09d4ba6..c87ea2a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -90,6 +90,7 @@
public class AccessibilityServiceInfo implements android.os.Parcelable {
method @NonNull public android.content.ComponentName getComponentName();
+ method public void setAccessibilityTool(boolean);
}
}
@@ -797,6 +798,7 @@
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+ field public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -824,6 +826,8 @@
}
public abstract class PackageManager {
+ method public abstract void addCrossProfileIntentFilter(@NonNull android.content.IntentFilter, int, int, int);
+ method public abstract void clearCrossProfileIntentFilters(int);
method @Deprecated @Nullable public final String getContentCaptureServicePackageName();
method @Nullable public String getDefaultTextClassifierPackageName();
method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 2e89ce8..8f6bfd3 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,6 +784,7 @@
mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
mInteractiveUiTimeout = other.mInteractiveUiTimeout;
flags = other.flags;
+ mIsAccessibilityTool = other.mIsAccessibilityTool;
}
private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
@@ -1112,6 +1113,26 @@
}
/**
+ * Sets whether the service is used to assist users with disabilities.
+ *
+ * <p>
+ * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
+ * </p>
+ *
+ * <p>
+ * This method is helpful for unit testing. However, this property is not dynamically
+ * configurable by a standard {@link AccessibilityService} so it's not possible to update the
+ * copy held by the system with this method.
+ * </p>
+ *
+ * @hide
+ */
+ @TestApi
+ public void setAccessibilityTool(boolean isAccessibilityTool) {
+ mIsAccessibilityTool = isAccessibilityTool;
+ }
+
+ /**
* Indicates if the service is used to assist users with disabilities.
*
* @return {@code true} if the property is set to true.
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f2ea060..9210958 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -41,6 +41,8 @@
import android.util.ArraySet;
import android.util.Pair;
+import com.android.internal.os.TimeoutRecord;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -440,10 +442,13 @@
/** Input dispatch timeout to a window, start the ANR process. Return the timeout extension,
* in milliseconds, or 0 to abort dispatch. */
- public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
+ public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem,
+ TimeoutRecord timeoutRecord);
+
public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
- boolean aboveSystem, String reason);
+ boolean aboveSystem, TimeoutRecord timeoutRecord);
+
/**
* App started responding to input events. This signal can be used to abort the ANR process and
* hide the ANR dialog.
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index a045157..8d57e32 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -550,6 +550,7 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+ info.setAccessibilityTool(true);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 47bec61..24fe0db 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -83,11 +83,11 @@
}
/** Internal interface for proximity callback. */
- public abstract static class ProximityUpdateCallbackInternal {
+ public interface ProximityUpdateCallbackInternal {
/**
* @param distance the estimated distance of the user (in meter)
* The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
*/
- public abstract void onProximityUpdate(double distance);
+ void onProximityUpdate(double distance);
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a3d595ef..e92be7c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1109,6 +1109,17 @@
public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f;
/**
+ * Enables the use of split screen aspect ratio. This allows an app to use all the available
+ * space in split mode avoiding letterboxing.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L;
+
+ /**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
*/
@@ -1317,8 +1328,8 @@
* Returns true if the activity has maximum or minimum aspect ratio.
* @hide
*/
- public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) {
- return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0;
+ public boolean hasFixedAspectRatio() {
+ return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
/**
@@ -1460,30 +1471,10 @@
}
/**
- * Returns the min aspect ratio of this activity.
- *
- * This takes into account the minimum aspect ratio as defined in the app's manifest and
- * possible overrides as per OVERRIDE_MIN_ASPECT_RATIO.
- *
- * In the rare cases where the manifest minimum aspect ratio is required, use
- * {@code getManifestMinAspectRatio}.
+ * Returns the min aspect ratio of this activity as defined in the manifest file.
* @hide
*/
- public float getMinAspectRatio(@ScreenOrientation int orientation) {
- if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
- isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
- && !isFixedOrientationPortrait(orientation))) {
- return mMinAspectRatio;
- }
-
- if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
- return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
- }
-
- if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
- return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
- }
-
+ public float getMinAspectRatio() {
return mMinAspectRatio;
}
@@ -1512,7 +1503,13 @@
}
}
- private boolean isChangeEnabled(long changeId) {
+ /**
+ * Checks if a changeId is enabled for the current user
+ * @param changeId The changeId to verify
+ * @return True of the changeId is enabled
+ * @hide
+ */
+ public boolean isChangeEnabled(long changeId) {
return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
UserHandle.getUserHandleForUid(applicationInfo.uid));
}
@@ -1633,12 +1630,9 @@
if (getMaxAspectRatio() != 0) {
pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
}
- final float minAspectRatio = getMinAspectRatio(screenOrientation);
+ final float minAspectRatio = getMinAspectRatio();
if (minAspectRatio != 0) {
pw.println(prefix + "minAspectRatio=" + minAspectRatio);
- if (getManifestMinAspectRatio() != minAspectRatio) {
- pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
- }
}
if (supportsSizeChanges) {
pw.println(prefix + "supportsSizeChanges=true");
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index dd517c9..5839b87 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -9473,6 +9473,7 @@
*/
@SuppressWarnings("HiddenAbstractMethod")
@UnsupportedAppUsage
+ @TestApi
public abstract void addCrossProfileIntentFilter(@NonNull IntentFilter filter,
@UserIdInt int sourceUserId, @UserIdInt int targetUserId, int flags);
@@ -9485,6 +9486,7 @@
*/
@SuppressWarnings("HiddenAbstractMethod")
@UnsupportedAppUsage
+ @TestApi
public abstract void clearCrossProfileIntentFilters(@UserIdInt int sourceUserId);
/**
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 0f1b39c..df1c0d7 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -892,7 +892,7 @@
* <tr><th colspan="7">Preview stabilization guaranteed stream configurations</th></tr>
* <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr>
* <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr>
- * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="4" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr>
+ * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="2" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr>
* <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td> <td>{@code JPEG / YUV}</td><td id="rb">{@code MAXIMUM }</td><td>Standard still imaging with stabilized preview.</td> </tr>
* <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p }</td><td>High-resolution recording with stabilized preview and recording stream.</td> </tr>
* </table><br>
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index b0b3e9e..52cef0f 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -374,6 +374,15 @@
public abstract Point getDisplaySurfaceDefaultSize(int displayId);
/**
+ * Get a new displayId which represents the display you want to mirror. If mirroring is not
+ * enabled on the display, {@link Display#INVALID_DISPLAY} will be returned.
+ *
+ * @param displayId The id of the display.
+ * @return The displayId that should be mirrored or INVALID_DISPLAY if mirroring is not enabled.
+ */
+ public abstract int getDisplayIdToMirror(int displayId);
+
+ /**
* Receives early interactivity changes from power manager.
*
* @param interactive The interactive state that the device is moving into.
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index e088f30..068df22 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
+import android.util.proto.ProtoOutputStream;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -97,6 +98,19 @@
}
}
+ void writePowerComponentModelProto(@NonNull ProtoOutputStream proto) {
+ for (int i = 0; i < POWER_COMPONENT_COUNT; i++) {
+ final int powerModel = getPowerModel(i);
+ if (powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) continue;
+
+ final long token = proto.start(BatteryUsageStatsAtomsProto.COMPONENT_MODELS);
+ proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.COMPONENT, i);
+ proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_MODEL,
+ powerModelToProtoEnum(powerModel));
+ proto.end(token);
+ }
+ }
+
/**
* Builder for DeviceBatteryConsumer.
*/
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index f4ca1b5..de2dec7 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -479,6 +479,21 @@
}
/**
+ * Returns the equivalent PowerModel enum for the specified power model.
+ * {@see BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage.PowerModel}
+ */
+ public static int powerModelToProtoEnum(@BatteryConsumer.PowerModel int powerModel) {
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.MEASURED_ENERGY;
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_PROFILE;
+ default:
+ return BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED;
+ }
+ }
+
+ /**
* Returns the name of the specified process state. Intended for logging and debugging.
*/
public static String processStateToString(@BatteryConsumer.ProcessState int processState) {
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index b76592d..7105b1a 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -276,7 +276,7 @@
/**
* Returns a battery consumer for the specified battery consumer type.
*/
- public BatteryConsumer getAggregateBatteryConsumer(
+ public AggregateBatteryConsumer getAggregateBatteryConsumer(
@AggregateBatteryConsumerScope int scope) {
return mAggregateBatteryConsumers[scope];
}
@@ -450,7 +450,7 @@
@NonNull
private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
- final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
+ final AggregateBatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
@@ -462,6 +462,9 @@
getDischargeDurationMs());
deviceBatteryConsumer.writeStatsProto(proto,
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
+ if (mIncludesPowerModels) {
+ deviceBatteryConsumer.writePowerComponentModelProto(proto);
+ }
writeUidBatteryConsumersProto(proto, maxRawSize);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e5de3e1..f69d6b0 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -125,6 +125,7 @@
boolean isUserUnlocked(int userId);
boolean isUserRunning(int userId);
boolean isUserForeground(int userId);
+ boolean isUserVisible(int userId);
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles(int userId);
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/NewUserResponse.java b/core/java/android/os/NewUserResponse.java
index c2aef8e..f48d9e5 100644
--- a/core/java/android/os/NewUserResponse.java
+++ b/core/java/android/os/NewUserResponse.java
@@ -61,4 +61,13 @@
public @UserManager.UserOperationResult int getOperationResult() {
return mOperationResult;
}
+
+ @Override
+ public String toString() {
+ return "NewUserResponse{"
+ + "mUser="
+ + mUser
+ + ", mOperationResult=" + mOperationResult
+ + '}';
+ }
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d14d2e4..16352b8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -90,6 +90,7 @@
public class UserManager {
private static final String TAG = "UserManager";
+ private static final boolean VERBOSE = false;
@UnsupportedAppUsage
private final IUserManager mService;
@@ -2056,10 +2057,10 @@
final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
switch (emulatedMode) {
case SYSTEM_USER_MODE_EMULATION_FULL:
- Log.d(TAG, "isHeadlessSystemUserMode(): emulating as false");
+ if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
return false;
case SYSTEM_USER_MODE_EMULATION_HEADLESS:
- Log.d(TAG, "isHeadlessSystemUserMode(): emulating as true");
+ if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
return true;
case SYSTEM_USER_MODE_EMULATION_DEFAULT:
case "": // property not set
@@ -2858,6 +2859,30 @@
}
/**
+ * Checks if the user is visible at the moment.
+ *
+ * <p>Roughly speaking, a "visible user" is a user that can present UI on at least one display.
+ * It includes:
+ *
+ * <ol>
+ * <li>The current foreground user in the main display.
+ * <li>Current background users in secondary displays (for example, passenger users on
+ * automotive, using the display associated with their seats).
+ * <li>Profile users (in the running / started state) of other visible users.
+ * </ol>
+ *
+ * @return whether the user is visible at the moment, as defined above.
+ */
+ @UserHandleAware
+ public boolean isUserVisible() {
+ try {
+ return mService.isUserVisible(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return whether the context user is running in an "unlocked" state.
* <p>
* On devices with direct boot, a user is unlocked only after they've
@@ -4166,7 +4191,11 @@
})
public boolean canAddMoreUsers(@NonNull String userType) {
try {
- return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+ if (userType.equals(USER_TYPE_FULL_GUEST)) {
+ return mService.canAddMoreUsersOfType(userType);
+ } else {
+ return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType);
+ }
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 9cd2325..2483f99 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -630,7 +630,7 @@
private static final List<String> PUBLIC_NAMESPACES =
Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
- NAMESPACE_DEVICE_POLICY_MANAGER);
+ NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
/**
* Privacy related properties definitions.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6fc2811..a7c7273 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5486,6 +5486,15 @@
public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
/**
+ * Whether desktop mode is enabled or not.
+ * 0 = off
+ * 1 = on
+ * @hide
+ */
+ @Readable
+ public static final String DESKTOP_MODE = "desktop_mode";
+
+ /**
* IMPORTANT: If you add a new public settings you also have to add it to
* PUBLIC_SETTINGS below. If the new setting is hidden you have to add
* it to PRIVATE_SETTINGS below. Also add a validator that can validate
@@ -5614,6 +5623,7 @@
PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT);
PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE);
PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE_VENDOR_HINT);
+ PRIVATE_SETTINGS.add(DESKTOP_MODE);
}
/**
@@ -10814,6 +10824,13 @@
"accessibility_floating_menu_migration_tooltip_prompt";
/**
+ * Setting that specifies whether the software cursor accessibility service is enabled.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED =
+ "accessibility_software_cursor_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
@@ -14257,7 +14274,7 @@
*
* @hide
*/
- public static final int DEFAULT_ENABLE_TARE = 0;
+ public static final int DEFAULT_ENABLE_TARE = 1;
/**
* Whether to enable the TARE AlarmManager economic policy or not.
@@ -17681,20 +17698,13 @@
"wear_activity_auto_resume_timeout_set_by_user";
/**
- * The maximum times that we are allowed to reset the activity auto-resume timeout
- * timer.
- * @hide
- */
- public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT =
- "wear_activity_auto_resume_timeout_max_reset_count";
-
- /**
* If burn in protection is enabled.
* @hide
*/
public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection";
/**
+
* Whether the device has combined location setting enabled.
* @hide
*/
@@ -17755,6 +17765,37 @@
* @hide
*/
public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled";
+
+ /** The status of the early updates process.
+ * @hide
+ */
+ public static final String EARLY_UPDATES_STATUS = "early_updates_status";
+
+ /**
+ * Early updates not started
+ * @hide
+ */
+ public static final int EARLY_UPDATES_STATUS_NOT_STARTED = 0;
+ /**
+ * Early updates started and in progress
+ * @hide
+ */
+ public static final int EARLY_UPDATES_STATUS_STARTED = 1;
+ /**
+ * Early updates completed and was successful
+ * @hide
+ */
+ public static final int EARLY_UPDATES_STATUS_SUCCESS = 2;
+ /**
+ * Early updates skipped
+ * @hide
+ */
+ public static final int EARLY_UPDATES_STATUS_SKIPPED = 3;
+ /**
+ * Early updates aborted due to timeout
+ * @hide
+ */
+ public static final int EARLY_UPDATES_STATUS_ABORTED = 4;
}
}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 7234145..ab71459 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -95,6 +95,16 @@
/** Limits the max value for the triggered audio channel. */
private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
+ /**
+ * The bundle key for proximity value
+ *
+ * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
+ *
+ * @hide
+ */
+ public static final String EXTRA_PROXIMITY_METERS =
+ "android.service.voice.extra.PROXIMITY_METERS";
+
/** Confidence level in the trigger outcome. */
@HotwordConfidenceLevelValue
private final int mConfidenceLevel;
@@ -197,6 +207,14 @@
* <p>The use of this method is discouraged, and support for it will be removed in future
* versions of Android.
*
+ * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+ * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+ * will be stored to enable proximity logic. The proximity meters is provided by the system,
+ * on devices that support detecting proximity of nearby users, to help disambiguate which
+ * nearby device should respond. When the proximity is unknown, the proximity value will not
+ * be stored. This mapping will be excluded from the max bundle size calculation because this
+ * mapping is included after the result is returned from the hotword detector service.
+ *
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
*/
@@ -315,8 +333,23 @@
"audioChannel");
}
if (!mExtras.isEmpty()) {
- Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0, getMaxBundleSize(),
- "extras");
+ // Remove the proximity key from the bundle before checking the bundle size. The
+ // proximity value is added after the privileged module and can avoid the
+ // maxBundleSize limitation.
+ if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
+ double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
+ mExtras.remove(EXTRA_PROXIMITY_METERS);
+ // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
+ // has parcelable size of 4, but the default bundle has parcelable size of 0.
+ if (mExtras.size() > 0) {
+ Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+ getMaxBundleSize(), "extras");
+ }
+ mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+ } else {
+ Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
+ getMaxBundleSize(), "extras");
+ }
}
}
@@ -513,6 +546,14 @@
* <p>The use of this method is discouraged, and support for it will be removed in future
* versions of Android.
*
+ * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+ * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+ * will be stored to enable proximity logic. The proximity meters is provided by the system,
+ * on devices that support detecting proximity of nearby users, to help disambiguate which
+ * nearby device should respond. When the proximity is unknown, the proximity value will not
+ * be stored. This mapping will be excluded from the max bundle size calculation because this
+ * mapping is included after the result is returned from the hotword detector service.
+ *
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
*/
@@ -813,6 +854,14 @@
* <p>The use of this method is discouraged, and support for it will be removed in future
* versions of Android.
*
+ * <p>After the trigger happens, a special case of proximity-related extra, with the key of
+ * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
+ * will be stored to enable proximity logic. The proximity meters is provided by the system,
+ * on devices that support detecting proximity of nearby users, to help disambiguate which
+ * nearby device should respond. When the proximity is unknown, the proximity value will not
+ * be stored. This mapping will be excluded from the max bundle size calculation because this
+ * mapping is included after the result is returned from the hotword detector service.
+ *
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
*/
@@ -882,10 +931,10 @@
}
@DataClass.Generated(
- time = 1625541522353L,
+ time = 1658357814396L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index a6f63e8..52dc342 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -89,9 +89,7 @@
// Callbacks should have the same configuration of the flags below to allow satisfying a pending
// node request on prefetch
- private static final int FLAGS_AFFECTING_REPORTED_DATA =
- AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ private static final int FLAGS_AFFECTING_REPORTED_DATA = AccessibilityNodeInfo.FLAG_REPORT_MASK;
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -167,6 +165,11 @@
return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
}
+ private boolean isVisibleToAccessibilityService(View view) {
+ return view != null && (!view.isAccessibilityDataPrivate()
+ || mA11yManager.isRequestFromAccessibilityTool());
+ }
+
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
@@ -358,7 +361,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
requestedView = findViewByAccessibilityId(accessibilityViewId);
if (requestedView != null && isShown(requestedView)) {
requestedNode = populateAccessibilityNodeInfoForView(
@@ -371,7 +374,7 @@
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
infos);
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
}
}
} finally {
@@ -396,7 +399,7 @@
}
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
@@ -478,7 +481,7 @@
|| viewId == null) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null) {
final int resolvedViewId = root.getContext().getResources()
@@ -494,7 +497,7 @@
mAddNodeInfosForViewId.reset();
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -542,7 +545,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
@@ -561,7 +564,7 @@
final int viewCount = foundViews.size();
for (int i = 0; i < viewCount; i++) {
View foundView = foundViews.get(i);
- if (isShown(foundView)) {
+ if (isShown(foundView) && isVisibleToAccessibilityService(foundView)) {
provider = foundView.getAccessibilityNodeProvider();
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
@@ -579,7 +582,7 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -627,7 +630,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
switch (focusType) {
@@ -642,6 +645,9 @@
if (!isShown(host)) {
break;
}
+ if (!isVisibleToAccessibilityService(host)) {
+ break;
+ }
// If the host has a provider ask this provider to search for the
// focus instead fetching all provider nodes to do the search here.
AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
@@ -662,6 +668,9 @@
if (!isShown(target)) {
break;
}
+ if (!isVisibleToAccessibilityService(target)) {
+ break;
+ }
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
focused = provider.findFocus(focusType);
@@ -675,7 +684,7 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
updateInfoForViewportAndReturnFindNodeResult(
focused, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -722,7 +731,7 @@
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
View nextView = root.focusSearch(direction);
@@ -731,7 +740,7 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
updateInfoForViewportAndReturnFindNodeResult(
next, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -778,9 +787,9 @@
mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ setAccessibilityFetchFlags(flags);
final View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && isShown(target)) {
+ if (target != null && isShown(target) && isVisibleToAccessibilityService(target)) {
mA11yManager.notifyPerformingAction(action);
if (action == R.id.accessibilityActionClickOnClickableSpan) {
// Handle this hidden action separately
@@ -799,7 +808,7 @@
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -823,9 +832,9 @@
return;
}
try {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
- AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- final View root = mViewRootImpl.mView;
+ setAccessibilityFetchFlags(
+ AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
+ final View root = getRootView();
if (root != null && isShown(root)) {
final View host = mViewRootImpl.mAccessibilityFocusedHost;
// If there is no accessibility focus host or it is not a descendant
@@ -849,7 +858,7 @@
}
}
} finally {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ resetAccessibilityFetchFlags();
}
}
@@ -869,7 +878,7 @@
|| mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- final View root = mViewRootImpl.mView;
+ final View root = getRootView();
if (root != null && isShown(root)) {
// trigger ACTION_OUTSIDE to notify windows
final long now = SystemClock.uptimeMillis();
@@ -882,12 +891,30 @@
private View findViewByAccessibilityId(int accessibilityId) {
if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
- return mViewRootImpl.mView;
+ return getRootView();
} else {
return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
}
}
+ private View getRootView() {
+ if (!isVisibleToAccessibilityService(mViewRootImpl.mView)) {
+ return null;
+ }
+ return mViewRootImpl.mView;
+ }
+
+ private void setAccessibilityFetchFlags(int flags) {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
+ mA11yManager.setRequestFromAccessibilityTool(
+ (flags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) != 0);
+ }
+
+ private void resetAccessibilityFetchFlags() {
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ mA11yManager.setRequestFromAccessibilityTool(false);
+ }
+
// The boundInScreen includes magnification effect, so we need to normalize it before
// determine the visibility.
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
@@ -1706,7 +1733,7 @@
@Override
public boolean test(View view) {
- if (view.getId() == mViewId && isShown(view)) {
+ if (view.getId() == mViewId && isShown(view) && isVisibleToAccessibilityService(view)) {
mInfos.add(view.createAccessibilityNodeInfo());
}
return false;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index d63c25a..5236fe7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -176,7 +176,9 @@
// If we have a new leash, make sure visibility is up-to-date, even though we
// didn't want to run an animation above.
- applyRequestedVisibilityToControl();
+ if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+ applyRequestedVisibilityToControl();
+ }
// Remove the surface that owned by last control when it lost.
if (!requestedVisible && lastControl == null) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5f6c24b..84f04c1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -172,7 +172,6 @@
private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
private static native long[] nativeGetPhysicalDisplayIds();
- private static native long nativeGetPrimaryPhysicalDisplayId();
private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
@@ -2395,15 +2394,6 @@
}
/**
- * Exposed to identify the correct display to apply the primary display orientation. Avoid using
- * for any other purpose.
- * @hide
- */
- public static long getPrimaryPhysicalDisplayId() {
- return nativeGetPrimaryPhysicalDisplayId();
- }
-
- /**
* @hide
*/
public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index d75ff2f..5721fa6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -422,7 +422,7 @@
public void relayout(WindowManager.LayoutParams attrs,
WindowlessWindowManager.ResizeCompleteCallback callback) {
mViewRoot.setLayoutParams(attrs, false);
- mViewRoot.setReportNextDraw(true /* syncBuffer */);
+ mViewRoot.setReportNextDraw(true /* syncBuffer */, "scvh_relayout");
mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70a5eda..8f9c5fe 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -119,10 +119,6 @@
final int[] mLocation = new int[2];
@UnsupportedAppUsage
- // Used to ensure the Surface remains valid between SurfaceHolder#lockCanvas and
- // SurfaceHolder#unlockCanvasAndPost calls. This prevents SurfaceView from destroying or
- // invalidating the Surface. This means this lock should be acquired when destroying the
- // BlastBufferQueue.
final ReentrantLock mSurfaceLock = new ReentrantLock();
@UnsupportedAppUsage
final Surface mSurface = new Surface(); // Current surface in use
@@ -727,18 +723,14 @@
private void releaseSurfaces(boolean releaseSurfacePackage) {
mSurfaceAlpha = 1f;
- try {
- mSurfaceLock.lock();
- mSurface.destroy();
+ mSurface.destroy();
+
+ synchronized (mSurfaceControlLock) {
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
}
- } finally {
- mSurfaceLock.unlock();
- }
- synchronized (mSurfaceControlLock) {
final Transaction transaction = new Transaction();
if (mSurfaceControl != null) {
transaction.remove(mSurfaceControl);
@@ -1240,21 +1232,16 @@
.build();
}
+ // Always recreate the IGBP for compatibility. This can be optimized in the future but
+ // the behavior change will need to be gated by SDK version.
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ }
mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
- // Always recreate the IGBP for compatibility. This can be optimized in the future but
- // the behavior change will need to be gated by SDK version.
- try {
- mSurfaceLock.lock();
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.destroy();
- }
- mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
- mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
- } finally {
- mSurfaceLock.unlock();
- }
+ mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
+ mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
}
@@ -1827,14 +1814,7 @@
// so the next connect will always work if we end up reusing
// the surface.
if (mSurface.isValid()) {
- // We need to grab this lock since mSurface.forceScopedDisconnect
- // will free buffers from the queue.
- try {
- mSurfaceLock.lock();
- mSurface.forceScopedDisconnect();
- } finally {
- mSurfaceLock.unlock();
- }
+ mSurface.forceScopedDisconnect();
}
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4893777..84edb3a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3085,6 +3085,45 @@
static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
/**
+ * Automatically determine whether the view should only allow interactions from
+ * {@link android.accessibilityservice.AccessibilityService}s with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ *
+ * <p>
+ * Accessibility interactions from services without {@code isAccessibilityTool} set to true are
+ * disallowed for any of the following conditions:
+ * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li>
+ * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li>
+ * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li>
+ * </p>
+ */
+ public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0x00000000;
+
+ /**
+ * Only allow interactions from {@link android.accessibilityservice.AccessibilityService}s
+ * with the {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool}
+ * property set to true.
+ */
+ public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 0x00000001;
+
+ /**
+ * Allow interactions from all {@link android.accessibilityservice.AccessibilityService}s,
+ * regardless of their
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property.
+ */
+ public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 0x00000002;
+
+ /** @hide */
+ @IntDef(prefix = { "ACCESSIBILITY_DATA_PRIVATE_" }, value = {
+ ACCESSIBILITY_DATA_PRIVATE_AUTO,
+ ACCESSIBILITY_DATA_PRIVATE_YES,
+ ACCESSIBILITY_DATA_PRIVATE_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessibilityDataPrivate {}
+
+ /**
* Mask for obtaining the bits which specify how to determine
* whether a view is important for accessibility.
*/
@@ -4527,6 +4566,14 @@
private CharSequence mAccessibilityPaneTitle;
/**
+ * Describes whether this view should only allow interactions from
+ * {@link android.accessibilityservice.AccessibilityService}s with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ */
+ private int mAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_AUTO;
+
+ /**
* Specifies the id of a view for which this view serves as a label for
* accessibility purposes.
*/
@@ -5919,6 +5966,10 @@
setImportantForAccessibility(a.getInt(attr,
IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
break;
+ case R.styleable.View_accessibilityDataPrivate:
+ setAccessibilityDataPrivate(a.getInt(attr,
+ ACCESSIBILITY_DATA_PRIVATE_AUTO));
+ break;
case R.styleable.View_accessibilityLiveRegion:
setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
break;
@@ -8518,6 +8569,11 @@
* is responsible for handling this call.
* </p>
* <p>
+ * If this view sets {@link #isAccessibilityDataPrivate()} then this view should only append
+ * sensitive information to an event that also sets
+ * {@link AccessibilityEvent#isAccessibilityDataPrivate()}.
+ * </p>
+ * <p>
* <em>Note:</em> Accessibility events of certain types are not dispatched for
* populating the event text via this method. For details refer to {@link AccessibilityEvent}.
* </p>
@@ -10419,7 +10475,7 @@
}
if ((mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+ & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0
&& Resources.resourceHasPackage(mID)) {
try {
String viewId = getResources().getResourceName(mID);
@@ -14402,14 +14458,75 @@
@UnsupportedAppUsage
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
+ if (isAccessibilityDataPrivate() && !AccessibilityManager.getInstance(
+ mContext).isRequestFromAccessibilityTool()) {
+ return false;
+ }
+
return (mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
|| isImportantForAccessibility();
}
return false;
}
/**
+ * Whether this view should restrict accessibility service access only to services that have the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ *
+ * <p>
+ * See default behavior provided by {@link #ACCESSIBILITY_DATA_PRIVATE_AUTO}. Otherwise,
+ * returns true for {@link #ACCESSIBILITY_DATA_PRIVATE_YES} or false for {@link
+ * #ACCESSIBILITY_DATA_PRIVATE_NO}.
+ * </p>
+ *
+ * @return True if this view should restrict accessibility service access to services that have
+ * the isAccessibilityTool property.
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility")
+ public boolean isAccessibilityDataPrivate() {
+ if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_YES) {
+ return true;
+ }
+ if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_NO) {
+ return false;
+ }
+
+ // Views inside FLAG_SECURE windows default to accessibilityDataPrivate.
+ if (mAttachInfo != null && mAttachInfo.mWindowSecure) {
+ return true;
+ }
+ // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate.
+ if (getFilterTouchesWhenObscured()) {
+ return true;
+ }
+
+ // Descendants of an accessibilityDataPrivate View are also accessibilityDataPrivate.
+ ViewParent parent = mParent;
+ while (parent instanceof View) {
+ if (((View) parent).isAccessibilityDataPrivate()) {
+ return true;
+ }
+ parent = parent.getParent();
+ }
+
+ // Otherwise, default to not accessibilityDataPrivate.
+ return false;
+ }
+
+ /**
+ * Specifies whether this view should only allow interactions from
+ * {@link android.accessibilityservice.AccessibilityService}s with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ */
+ public void setAccessibilityDataPrivate(
+ @AccessibilityDataPrivate int accessibilityDataPrivate) {
+ mAccessibilityDataPrivate = accessibilityDataPrivate;
+ }
+
+ /**
* Returns whether the View is considered actionable from
* accessibility perspective. Such view are important for
* accessibility.
@@ -30096,6 +30213,11 @@
int mWindowVisibility;
/**
+ * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.
+ */
+ boolean mWindowSecure;
+
+ /**
* Indicates the time at which drawing started to occur.
*/
@UnsupportedAppUsage
@@ -30272,8 +30394,8 @@
/**
* Flags related to accessibility processing.
*
- * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
+ * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
*/
int mAccessibilityFetchFlags;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 84dadca..f79f0d4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -587,8 +587,21 @@
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
-
boolean mReportNextDraw;
+ /** Set only while mReportNextDraw=true, indicating the last reason that was triggered */
+ String mLastReportNextDrawReason;
+ /** The reaason the last call to performDraw() returned false */
+ String mLastPerformDrawSkippedReason;
+ /** The reason the last call to performTraversals() returned without drawing */
+ String mLastPerformTraversalsSkipDrawReason;
+ /** The state of the local sync, if one is in progress. Can be one of the states below. */
+ int mLocalSyncState;
+
+ // The possible states of the local sync, see createSyncIfNeeded()
+ private final int LOCAL_SYNC_NONE = 0;
+ private final int LOCAL_SYNC_PENDING = 1;
+ private final int LOCAL_SYNC_RETURNED = 2;
+ private final int LOCAL_SYNC_MERGED = 3;
/**
* Set whether the draw should send the buffer to system server. When set to true, VRI will
@@ -1836,7 +1849,7 @@
mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
if (msg == MSG_RESIZED_REPORT) {
- reportNextDraw();
+ reportNextDraw("resized");
}
if (mView != null && (frameChanged || configChanged)) {
@@ -2741,6 +2754,8 @@
}
private void performTraversals() {
+ mLastPerformTraversalsSkipDrawReason = null;
+
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
@@ -2750,12 +2765,14 @@
}
if (host == null || !mAdded) {
+ mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";
return;
}
mIsInTraversal = true;
mWillDrawSoon = true;
boolean cancelDraw = false;
+ String cancelReason = null;
boolean isSyncRequest = false;
boolean windowSizeMayChange = false;
@@ -2822,6 +2839,7 @@
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
+ mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
@@ -3038,13 +3056,14 @@
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
== RELAYOUT_RES_CANCEL_AND_REDRAW;
+ cancelReason = "relayout";
final boolean dragResizing = mPendingDragResizing;
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
if (DEBUG_BLAST) {
Log.d(mTag, "Relayout called with blastSync");
}
- reportNextDraw();
+ reportNextDraw("relayout");
mSyncBuffer = true;
isSyncRequest = true;
if (!cancelDraw) {
@@ -3147,6 +3166,7 @@
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
+ mLastPerformTraversalsSkipDrawReason = "oom_initialize_renderer";
return;
}
}
@@ -3184,6 +3204,7 @@
mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
+ mLastPerformTraversalsSkipDrawReason = "oom_update_surface";
return;
}
}
@@ -3349,6 +3370,7 @@
if (mCheckIfCanDraw) {
try {
cancelDraw = mWindowSession.cancelDraw(mWindow);
+ cancelReason = "wm_sync";
if (DEBUG_BLAST) {
Log.d(mTag, "cancelDraw returned " + cancelDraw);
}
@@ -3571,19 +3593,21 @@
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
- reportNextDraw();
+ reportNextDraw("first_relayout");
}
mCheckIfCanDraw = isSyncRequest || cancelDraw;
- boolean cancelAndRedraw =
- mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync);
+ boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ boolean cancelAndRedraw = cancelDueToPreDrawListener
+ || (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
createSyncIfNeeded();
mDrewOnceForSync = true;
}
if (!isViewVisible) {
+ mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
@@ -3595,6 +3619,9 @@
mSyncBufferCallback.onBufferReady(null);
}
} else if (cancelAndRedraw) {
+ mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
+ ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
+ : "cancel_" + cancelReason;
// Try again
scheduleTraversals();
} else {
@@ -3618,11 +3645,13 @@
if (!cancelAndRedraw) {
mReportNextDraw = false;
+ mLastReportNextDrawReason = null;
mSyncBufferCallback = null;
mSyncBuffer = false;
if (isInLocalSync()) {
mSyncGroup.markSyncReady();
mSyncGroup = null;
+ mLocalSyncState = LOCAL_SYNC_NONE;
}
}
}
@@ -3634,12 +3663,15 @@
}
final int seqId = mSyncSeqId;
+ mLocalSyncState = LOCAL_SYNC_PENDING;
mSyncGroup = new SurfaceSyncGroup(transaction -> {
+ mLocalSyncState = LOCAL_SYNC_RETURNED;
// Callback will be invoked on executor thread so post to main thread.
mHandler.postAtFrontOfQueue(() -> {
if (transaction != null) {
mSurfaceChangedTransaction.merge(transaction);
}
+ mLocalSyncState = LOCAL_SYNC_MERGED;
reportDrawFinished(seqId);
});
});
@@ -4353,9 +4385,12 @@
}
private boolean performDraw() {
+ mLastPerformDrawSkippedReason = null;
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
+ mLastPerformDrawSkippedReason = "screen_off";
return false;
} else if (mView == null) {
+ mLastPerformDrawSkippedReason = "no_root_view";
return false;
}
@@ -8458,6 +8493,21 @@
if (mTraversalScheduled) {
writer.println(innerPrefix + " (barrier=" + mTraversalBarrier + ")");
}
+ writer.println(innerPrefix + "mReportNextDraw=" + mReportNextDraw);
+ if (mReportNextDraw) {
+ writer.println(innerPrefix + " (reason=" + mLastReportNextDrawReason + ")");
+ }
+ if (mLastPerformTraversalsSkipDrawReason != null) {
+ writer.println(innerPrefix + "mLastPerformTraversalsFailedReason="
+ + mLastPerformTraversalsSkipDrawReason);
+ }
+ if (mLastPerformDrawSkippedReason != null) {
+ writer.println(innerPrefix + "mLastPerformDrawFailedReason="
+ + mLastPerformDrawSkippedReason);
+ }
+ if (mLocalSyncState != LOCAL_SYNC_NONE) {
+ writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+ }
writer.println(innerPrefix + "mIsAmbientMode=" + mIsAmbientMode);
writer.println(innerPrefix + "mUnbufferedInputSource="
+ Integer.toHexString(mUnbufferedInputSource));
@@ -9958,11 +10008,12 @@
}
}
- private void reportNextDraw() {
+ private void reportNextDraw(String reason) {
if (DEBUG_BLAST) {
Log.d(mTag, "reportNextDraw " + Debug.getCallers(5));
}
mReportNextDraw = true;
+ mLastReportNextDrawReason = reason;
}
/**
@@ -9975,11 +10026,12 @@
* @param syncBuffer If true, the transaction that contains the buffer from the draw should be
* sent to system to be synced. If false, VRI will not try to sync the buffer,
* but only report back that a buffer was drawn.
+ * @param reason A debug string indicating the reason for reporting the next draw
* @hide
*/
- public void setReportNextDraw(boolean syncBuffer) {
+ public void setReportNextDraw(boolean syncBuffer, String reason) {
mSyncBuffer = syncBuffer;
- reportNextDraw();
+ reportNextDraw(reason);
invalidate();
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index e246634..2c2ae06 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -45,6 +45,30 @@
public abstract class ViewStructure {
/**
+ * Key used for writing active child view information to the content capture bundle.
+ *
+ * The value stored under this key will be an ordered list of Autofill IDs of child views.
+ *
+ * TODO(b/241498401): Add @TestApi in Android U
+ * @hide
+ */
+ public static final String EXTRA_ACTIVE_CHILDREN_IDS =
+ "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS";
+
+ /**
+ * Key used for writing the first active child's position to the content capture bundle.
+ *
+ * When active child view information is provided under the
+ * {@link #EXTRA_ACTIVE_CHILDREN_IDS}, the value stored under this key will be the
+ * 0-based position of the first child view in the list relative to the positions of child views
+ * in the containing View's dataset.
+ *
+ * TODO(b/241498401): Add @TestApi in Android U
+ * @hide */
+ public static final String EXTRA_FIRST_ACTIVE_POSITION =
+ "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION";
+
+ /**
* Set the identifier for this view.
*
* @param id The view's identifier, as per {@link View#getId View.getId()}.
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 2c077c3..c9526fd 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -74,6 +74,9 @@
* that the listener will be immediately called. */
private boolean mWindowShown;
+ // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw
+ private String mLastDispatchOnPreDrawCanceledReason;
+
private boolean mAlive = true;
/**
@@ -1161,6 +1164,7 @@
*/
@SuppressWarnings("unchecked")
public final boolean dispatchOnPreDraw() {
+ mLastDispatchOnPreDrawCanceledReason = null;
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
@@ -1168,7 +1172,11 @@
try {
int count = access.size();
for (int i = 0; i < count; i++) {
- cancelDraw |= !(access.get(i).onPreDraw());
+ final OnPreDrawListener preDrawListener = access.get(i);
+ cancelDraw |= !(preDrawListener.onPreDraw());
+ if (cancelDraw) {
+ mLastDispatchOnPreDrawCanceledReason = preDrawListener.getClass().getName();
+ }
}
} finally {
listeners.end();
@@ -1178,6 +1186,15 @@
}
/**
+ * @return the reason that the last call to dispatchOnPreDraw() returned true to cancel the
+ * current draw, or null if the last call did not cancel.
+ * @hide
+ */
+ final String getLastDispatchOnPreDrawCanceledReason() {
+ return mLastDispatchOnPreDrawCanceledReason;
+ }
+
+ /**
* Notifies registered listeners that the window is now shown
* @hide
*/
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6f69361..fe8d64f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4470,10 +4470,22 @@
changes |= LAYOUT_CHANGED;
}
- if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) {
+ if (paramsForRotation != o.paramsForRotation) {
+ if ((changes & LAYOUT_CHANGED) == 0) {
+ if (paramsForRotation != null && o.paramsForRotation != null
+ && paramsForRotation.length == o.paramsForRotation.length) {
+ for (int i = paramsForRotation.length - 1; i >= 0; i--) {
+ if (hasLayoutDiff(paramsForRotation[i], o.paramsForRotation[i])) {
+ changes |= LAYOUT_CHANGED;
+ break;
+ }
+ }
+ } else {
+ changes |= LAYOUT_CHANGED;
+ }
+ }
paramsForRotation = o.paramsForRotation;
checkNonRecursiveParams();
- changes |= LAYOUT_CHANGED;
}
if (mWallpaperTouchEventsEnabled != o.mWallpaperTouchEventsEnabled) {
@@ -4484,6 +4496,21 @@
return changes;
}
+ /**
+ * Returns {@code true} if the 2 params may have difference results of
+ * {@link WindowLayout#computeFrames}.
+ */
+ private static boolean hasLayoutDiff(LayoutParams a, LayoutParams b) {
+ return a.width != b.width || a.height != b.height || a.x != b.x || a.y != b.y
+ || a.horizontalMargin != b.horizontalMargin
+ || a.verticalMargin != b.verticalMargin
+ || a.layoutInDisplayCutoutMode != b.layoutInDisplayCutoutMode
+ || a.gravity != b.gravity || !Arrays.equals(a.providedInsets, b.providedInsets)
+ || a.mFitInsetsTypes != b.mFitInsetsTypes
+ || a.mFitInsetsSides != b.mFitInsetsSides
+ || a.mFitInsetsIgnoringVisibility != b.mFitInsetsIgnoringVisibility;
+ }
+
@Override
public String debug(String output) {
output += "Contents of " + this + ":";
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index cd0dd1d..2db0dcb 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -46,15 +46,17 @@
* </p>
* <p>
* The main purpose of an accessibility event is to communicate changes in the UI to an
- * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
- * if needed the user interface by examining the View hierarchy, as represented by a tree of
- * {@link AccessibilityNodeInfo}s (snapshot of a View state)
- * which can be used for exploring the window content. Note that the privilege for accessing
- * an event's source, thus the window content, has to be explicitly requested. For more
+ * {@link android.accessibilityservice.AccessibilityService}. If needed, the service may then
+ * inspect the user interface by examining the View hierarchy through the event's
+ * {@link #getSource() source}, as represented by a tree of {@link AccessibilityNodeInfo}s (snapshot
+ * of a View state) which can be used for exploring the window content. Note that the privilege for
+ * accessing an event's source, thus the window content, has to be explicitly requested. For more
* details refer to {@link android.accessibilityservice.AccessibilityService}. If an
* accessibility service has not requested to retrieve the window content the event will
- * not contain reference to its source. Also for events of type
- * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
+ * not contain reference to its source. <strong>Note: </strong> for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available, and Views that set
+ * {@link android.view.View#isAccessibilityDataPrivate()} may not populate all event properties on
+ * events sent from higher up in the view hierarchy.
* </p>
* <p>
* This class represents various semantically different accessibility event
@@ -1092,6 +1094,47 @@
}
/**
+ * Whether the event should only be delivered to an
+ * {@link android.accessibilityservice.AccessibilityService} with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ *
+ * <p>
+ * Initial value matches the {@link android.view.View#isAccessibilityDataPrivate} property from
+ * the event's source node, if present, or false by default.
+ * </p>
+ *
+ * @return True if the event should be delivered only to isAccessibilityTool services, false
+ * otherwise.
+ * @see #setAccessibilityDataPrivate
+ */
+ @Override
+ public boolean isAccessibilityDataPrivate() {
+ return super.isAccessibilityDataPrivate();
+ }
+
+ /**
+ * Sets whether the event should only be delivered to an
+ * {@link android.accessibilityservice.AccessibilityService} with the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
+ * set to true.
+ *
+ * <p>
+ * This will be set automatically based on the event's source (if present). If creating and
+ * sending an event directly through {@link AccessibilityManager} (where an event may have
+ * no source) then this method must be called explicitly if you want non-default behavior.
+ * </p>
+ *
+ * @param accessibilityDataPrivate True if the event should be delivered only to
+ * isAccessibilityTool services, false otherwise.
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ @Override
+ public void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
+ super.setAccessibilityDataPrivate(accessibilityDataPrivate);
+ }
+
+ /**
* Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
*
* @see #SPEECH_STATE_SPEAKING_START
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 9e3195a..e89f836 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -276,6 +276,8 @@
private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
+ private boolean mRequestFromAccessibilityTool;
+
/**
* Map from a view's accessibility id to the list of request preparers set for that view
*/
@@ -983,6 +985,39 @@
}
/**
+ * Whether the current accessibility request comes from an
+ * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
+ * property set to true.
+ *
+ * <p>
+ * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate
+ * your nodes.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo}
+ * request is in progress, can change from one request to another, and has no meaning when a
+ * request is not in progress.
+ * </p>
+ *
+ * @return True if the current request is from a tool that sets isAccessibilityTool.
+ */
+ public boolean isRequestFromAccessibilityTool() {
+ return mRequestFromAccessibilityTool;
+ }
+
+ /**
+ * Specifies whether the current accessibility request comes from an
+ * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
+ * property set to true.
+ *
+ * @hide
+ */
+ public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) {
+ mRequestFromAccessibilityTool = requestFromAccessibilityTool;
+ }
+
+ /**
* Registers a {@link AccessibilityRequestPreparer}.
*/
public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 953f261..15718c4 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -217,14 +217,29 @@
@Retention(RetentionPolicy.SOURCE)
public @interface PrefetchingStrategy {}
- /** @hide */
- public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
+ /**
+ * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @hide
+ */
+ public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
+
+ /**
+ * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS
+ * @hide
+ */
+ public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100;
+
+ /**
+ * @see AccessibilityServiceInfo#isAccessibilityTool()
+ * @hide
+ */
+ public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200;
/** @hide */
- public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
-
- /** @hide */
- public static final int FLAG_REPORT_MASK = 0x00000180;
+ public static final int FLAG_REPORT_MASK =
+ FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+ | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
+ | FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
// Actions.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 036316e..789c740 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -72,6 +72,7 @@
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
+ private static final int PROPERTY_ACCESSIBILITY_DATA_PRIVATE = 0x00000400;
private static final int GET_SOURCE_PREFETCH_FLAGS =
AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
@@ -159,6 +160,8 @@
important = root.isImportantForAccessibility();
rootViewId = root.getAccessibilityViewId();
mSourceWindowId = root.getAccessibilityWindowId();
+ setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE,
+ root.isAccessibilityDataPrivate());
}
setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -388,6 +391,23 @@
}
/**
+ * @see AccessibilityEvent#isAccessibilityDataPrivate
+ * @hide
+ */
+ boolean isAccessibilityDataPrivate() {
+ return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE);
+ }
+
+ /**
+ * @see AccessibilityEvent#setAccessibilityDataPrivate
+ * @hide
+ */
+ void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
+ enforceNotSealed();
+ setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, accessibilityDataPrivate);
+ }
+
+ /**
* Gets the number of items that can be visited.
*
* @return The number of items.
@@ -941,6 +961,8 @@
appendUnless(false, PROPERTY_CHECKED, builder);
appendUnless(false, PROPERTY_FULL_SCREEN, builder);
appendUnless(false, PROPERTY_SCROLLABLE, builder);
+ appendUnless(false, PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, builder);
+ appendUnless(false, PROPERTY_ACCESSIBILITY_DATA_PRIVATE, builder);
append(builder, "BeforeText", mBeforeText);
append(builder, "FromIndex", mFromIndex);
@@ -974,6 +996,8 @@
case PROPERTY_SCROLLABLE: return "Scrollable";
case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
return "ImportantForAccessibility";
+ case PROPERTY_ACCESSIBILITY_DATA_PRIVATE:
+ return "AccessibilityDataPrivate";
default: return Integer.toHexString(prop);
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index ba4176f..db4ac5d 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -55,6 +55,15 @@
/**
* Called when a node has been added to the screen and is visible to the user.
*
+ * On API level 33, this event may be re-sent with additional information if a view's children
+ * have changed, e.g. scrolling Views inside of a ListView. This information will be stored in
+ * the extras Bundle associated with the event's ViewNode. Within the Bundle, the
+ * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" key may be used to get a list of
+ * Autofill IDs of active child views, and the
+ * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" key may be used to get the 0-based
+ * position of the first active child view in the list relative to the positions of child views
+ * in the container View's dataset.
+ *
* <p>The metadata of the node is available through {@link #getViewNode()}.
*/
public static final int TYPE_VIEW_APPEARED = 1;
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 48d2970..1664637 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -280,6 +280,15 @@
"service_explicitly_enabled";
/**
+ * Device config property used by {@code android.widget.AbsListView} to determine whether or
+ * not it should report the positions of its children to Content Capture.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN =
+ "report_list_view_children";
+
+ /**
* Maximum number of events that are buffered before sent to the app.
*
* @hide
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0b4a298..0c03d10 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3605,7 +3605,9 @@
* specifying one of those statically defined subtypes in {@code subtypes}.</p>
*
* @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
+ * If the imiId is {@code null}, system would do nothing for this operation.
* @param subtypes subtypes will be added as additional subtypes of the current input method.
+ * If the subtypes is {@code null}, system would do nothing for this operation.
* @deprecated For IMEs that have already implemented features like customizable/downloadable
* keyboard layouts/languages, please start migration to other approaches. One idea
* would be exposing only one unified {@link InputMethodSubtype} then implement
@@ -3618,6 +3620,11 @@
mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId());
}
+ /**
+ * Returns the last used {@link InputMethodSubtype} in system history.
+ *
+ * @return the last {@link InputMethodSubtype}, {@code null} if last IME have no subtype.
+ */
@Nullable
public InputMethodSubtype getLastInputMethodSubtype() {
return mServiceInvoker.getLastInputMethodSubtype(UserHandle.myUserId());
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1cb96b1..1e1b9ba 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -20,6 +20,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -37,6 +38,7 @@
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.Trace;
+import android.provider.DeviceConfig;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -65,6 +67,7 @@
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
+import android.view.ViewStructure;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -73,6 +76,9 @@
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import android.view.autofill.AutofillId;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -642,6 +648,23 @@
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
+ * Indicates that reporting positions of child views to content capture is enabled via
+ * DeviceConfig.
+ */
+ private static boolean sContentCaptureReportingEnabledByDeviceConfig = false;
+
+ /**
+ * Listens for changes to DeviceConfig properties and updates stored values accordingly.
+ */
+ private static DeviceConfig.OnPropertiesChangedListener sDeviceConfigChangeListener = null;
+
+ /**
+ * Indicates that child positions of views should be reported to Content Capture the next time
+ * that active views are refreshed.
+ */
+ private boolean mReportChildrenToContentCaptureOnNextUpdate = true;
+
+ /**
* Helper object that renders and controls the fast scroll thumb.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768941)
@@ -858,8 +881,44 @@
public void adjustListItemSelectionBounds(Rect bounds);
}
+ private static class DeviceConfigChangeListener
+ implements DeviceConfig.OnPropertiesChangedListener {
+ @Override
+ public void onPropertiesChanged(
+ @NonNull DeviceConfig.Properties properties) {
+ if (!DeviceConfig.NAMESPACE_CONTENT_CAPTURE.equals(properties.getNamespace())) {
+ return;
+ }
+
+ for (String key : properties.getKeyset()) {
+ if (!ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN
+ .equals(key)) {
+ continue;
+ }
+
+ sContentCaptureReportingEnabledByDeviceConfig = properties.getBoolean(key,
+ false);
+ }
+ }
+ }
+
+ private static void setupDeviceConfigProperties() {
+ if (sDeviceConfigChangeListener == null) {
+ sContentCaptureReportingEnabledByDeviceConfig = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN,
+ false);
+ sDeviceConfigChangeListener = new DeviceConfigChangeListener();
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ sDeviceConfigChangeListener);
+ }
+ }
+
public AbsListView(Context context) {
super(context);
+ setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context);
mEdgeGlowTop = new EdgeEffect(context);
initAbsListView();
@@ -882,6 +941,7 @@
public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ setupDeviceConfigProperties();
mEdgeGlowBottom = new EdgeEffect(context, attrs);
mEdgeGlowTop = new EdgeEffect(context, attrs);
initAbsListView();
@@ -4802,6 +4862,14 @@
mOnScrollListener.onScrollStateChanged(this, newState);
}
}
+
+ // When scrolling, we want to report changes in the active children to Content Capture,
+ // so set the flag to report on the next update only when scrolling has stopped or a fling
+ // scroll is performed.
+ if (newState == OnScrollListener.SCROLL_STATE_IDLE
+ || newState == OnScrollListener.SCROLL_STATE_FLING) {
+ mReportChildrenToContentCaptureOnNextUpdate = true;
+ }
}
/**
@@ -6763,10 +6831,77 @@
mRecycler.mRecyclerListener = listener;
}
+ /**
+ * {@inheritDoc}
+ *
+ * This method will initialize the fields of the {@link ViewStructure}
+ * using the base implementation in {@link View}. On API level 33 and higher, it may also
+ * write information about the positions of active views to the extras bundle provided by the
+ * {@link ViewStructure}.
+ *
+ * NOTE: When overriding this method on API level 33, if not calling super() or if changing the
+ * logic for child views, be sure to provide values for the first active child view position and
+ * the list of active child views in the {@link ViewStructure}'s extras {@link Bundle} using the
+ * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" and
+ * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" keys.
+ *
+ * @param structure {@link ViewStructure} to be filled in with structured view data.
+ * @param flags optional flags.
+ *
+ * @see View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ */
+ @Override
+ public void onProvideContentCaptureStructure(
+ @NonNull ViewStructure structure, int flags) {
+ super.onProvideContentCaptureStructure(structure, flags);
+ if (!sContentCaptureReportingEnabledByDeviceConfig) {
+ return;
+ }
+
+ Bundle extras = structure.getExtras();
+
+ if (extras == null) {
+ Log.wtf(TAG, "Unexpected null extras Bundle in ViewStructure");
+ return;
+ }
+
+ int childCount = getChildCount();
+ ArrayList<AutofillId> idsList = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; ++i) {
+ View activeView = getChildAt(i);
+ if (activeView == null) {
+ continue;
+ }
+
+ idsList.add(activeView.getAutofillId());
+ }
+
+ extras.putParcelableArrayList(ViewStructure.EXTRA_ACTIVE_CHILDREN_IDS,
+ idsList);
+
+ extras.putInt(ViewStructure.EXTRA_FIRST_ACTIVE_POSITION,
+ getFirstVisiblePosition());
+ }
+
+ private void reportActiveViewsToContentCapture() {
+ if (!sContentCaptureReportingEnabledByDeviceConfig) {
+ return;
+ }
+
+ ContentCaptureSession session = getContentCaptureSession();
+ if (session != null) {
+ ViewStructure structure = session.newViewStructure(this);
+ onProvideContentCaptureStructure(structure, /* flags= */ 0);
+ session.notifyViewAppeared(structure);
+ }
+ }
+
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
+ mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
@@ -6775,6 +6910,7 @@
@Override
public void onInvalidated() {
super.onInvalidated();
+ mReportChildrenToContentCaptureOnNextUpdate = true;
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
@@ -7093,6 +7229,11 @@
lp.scrappedFromPosition = firstActivePosition + i;
}
}
+
+ if (mReportChildrenToContentCaptureOnNextUpdate && childCount > 0) {
+ AbsListView.this.reportActiveViewsToContentCapture();
+ mReportChildrenToContentCaptureOnNextUpdate = false;
+ }
}
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b233e54..b21c5b3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,6 +226,8 @@
final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
boolean mAllowUndo = true;
+ private int mLastToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
private final MetricsLogger mMetricsLogger = new MetricsLogger();
// Cursor Controllers.
@@ -1732,6 +1734,9 @@
@VisibleForTesting
public void onTouchEvent(MotionEvent event) {
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
+
+ mLastToolType = event.getToolType(event.getActionIndex());
+
mLastButtonState = event.getButtonState();
if (filterOutEvent) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1784,7 +1789,7 @@
}
private void showFloatingToolbar() {
- if (mTextActionMode != null) {
+ if (mTextActionMode != null && showUIForFingerInput()) {
// Delay "show" so it doesn't interfere with click confirmations
// or double-clicks that could "dismiss" the floating toolbar.
int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1864,7 +1869,8 @@
final CursorController cursorController = mTextView.hasSelection()
? getSelectionController() : getInsertionController();
if (cursorController != null && !cursorController.isActive()
- && !cursorController.isCursorBeingModified()) {
+ && !cursorController.isCursorBeingModified()
+ && showUIForFingerInput()) {
cursorController.show();
}
}
@@ -2515,6 +2521,10 @@
return false;
}
+ if (!showUIForFingerInput()) {
+ return false;
+ }
+
ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
registerOnBackInvokedCallback();
@@ -2667,7 +2677,7 @@
mTextView.postDelayed(mShowSuggestionRunnable,
ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
- if (shouldInsertCursor) {
+ if (shouldInsertCursor && showUIForFingerInput()) {
getInsertionController().show();
} else {
getInsertionController().hide();
@@ -5397,7 +5407,8 @@
final PointF showPosInView = new PointF();
final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
&& !tooLargeTextForMagnifier()
- && obtainMagnifierShowCoordinates(event, showPosInView);
+ && obtainMagnifierShowCoordinates(event, showPosInView)
+ && showUIForFingerInput();
if (shouldShow) {
// Make the cursor visible and stop blinking.
mRenderCursorRegardlessTiming = true;
@@ -6343,6 +6354,15 @@
}
}
+ /**
+ * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
+ *
+ * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
+ */
+ public boolean showUIForFingerInput() {
+ return mLastToolType != MotionEvent.TOOL_TYPE_MOUSE;
+ }
+
/** Controller for the insertion cursor. */
@VisibleForTesting
public class InsertionPointCursorController implements CursorController {
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index bd0a516..56310ae 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -11,3 +11,5 @@
per-file TextView*,EditText.java,Editor.java,EditorTouchState.java = file:../text/OWNERS
per-file SpellChecker.java = file:../view/inputmethod/OWNERS
+
+per-file RemoteViews* = file:../appwidget/OWNERS
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 5eec054..a339062 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1587,7 +1587,13 @@
public BitmapCache(Parcel source) {
mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
- mBitmapHashes = source.readSparseIntArray();
+ mBitmapHashes = new SparseIntArray();
+ for (int i = 0; i < mBitmaps.size(); i++) {
+ Bitmap b = mBitmaps.get(i);
+ if (b != null) {
+ mBitmapHashes.put(b.hashCode(), i);
+ }
+ }
}
public int getBitmapId(Bitmap b) {
@@ -1603,7 +1609,7 @@
b = b.asShared();
}
mBitmaps.add(b);
- mBitmapHashes.put(mBitmaps.size() - 1, hash);
+ mBitmapHashes.put(hash, mBitmaps.size() - 1);
mBitmapMemory = -1;
return (mBitmaps.size() - 1);
}
@@ -1620,7 +1626,6 @@
public void writeBitmapsToParcel(Parcel dest, int flags) {
dest.writeTypedList(mBitmaps, flags);
- dest.writeSparseIntArray(mBitmapHashes);
}
public int getBitmapMemory() {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a0ec48b..54a415c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,11 @@
final SelectionModifierCursorController controller = mEditor.getSelectionController();
if (controller != null
&& (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
- controller.show();
+ if (mEditor.showUIForFingerInput()) {
+ controller.show();
+ } else {
+ controller.hide();
+ }
}
if (result != null) {
switch (actionMode) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe0cbcb..3f87ec2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12076,6 +12076,13 @@
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
super.onPopulateAccessibilityEventInternal(event);
+ if (this.isAccessibilityDataPrivate() && !event.isAccessibilityDataPrivate()) {
+ // This view's accessibility data is private, but another view that generated this event
+ // is not, so don't append this view's text to the event in order to prevent sharing
+ // this view's contents with non-accessibility-tool services.
+ return;
+ }
+
final CharSequence text = getTextForAccessibility();
if (!TextUtils.isEmpty(text)) {
event.getText().add(text);
@@ -12489,7 +12496,7 @@
RectF[] boundingRects = new RectF[positionInfoLength];
final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
populateCharacterBounds(builder, positionInfoStartIndex,
- positionInfoStartIndex + positionInfoLength,
+ Math.min(positionInfoStartIndex + positionInfoLength, length()),
viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
for (int i = 0; i < positionInfoLength; i++) {
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 2126166..ff7ea31 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -269,6 +269,20 @@
}
/**
+ * Sets whether a task should be translucent. When {@code false}, the existing translucent of
+ * the task applies, but when {@code true} the task will be forced to be translucent.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setForceTranslucent(
+ @NonNull WindowContainerToken container, boolean forceTranslucent) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mForceTranslucent = forceTranslucent;
+ chg.mChangeMask |= Change.CHANGE_FORCE_TRANSLUCENT;
+ return this;
+ }
+
+ /**
* Used in conjunction with a shell-transition call (usually finishTransition). This is
* basically a message to the transition system that a particular task should NOT go into
* PIP even though it normally would. This is to deal with some edge-case situations where
@@ -854,11 +868,13 @@
public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
+ public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
+ private boolean mForceTranslucent = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -878,6 +894,7 @@
mFocusable = in.readBoolean();
mHidden = in.readBoolean();
mIgnoreOrientationRequest = in.readBoolean();
+ mForceTranslucent = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -923,6 +940,9 @@
if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
}
+ if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ mForceTranslucent = other.mForceTranslucent;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -973,6 +993,15 @@
return mIgnoreOrientationRequest;
}
+ /** Gets the requested force translucent state. */
+ public boolean getForceTranslucent() {
+ if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) == 0) {
+ throw new RuntimeException("Force translucent not set. "
+ + "Check CHANGE_FORCE_TRANSLUCENT first");
+ }
+ return mForceTranslucent;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1050,6 +1079,7 @@
dest.writeBoolean(mFocusable);
dest.writeBoolean(mHidden);
dest.writeBoolean(mIgnoreOrientationRequest);
+ dest.writeBoolean(mForceTranslucent);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 1d396be..0730f3d 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -325,7 +325,8 @@
public boolean checkApplicationCallbackRegistration(int priority,
OnBackInvokedCallback callback) {
if (!mApplicationCallBackEnabled
- && !(callback instanceof CompatOnBackInvokedCallback)) {
+ && !(callback instanceof CompatOnBackInvokedCallback)
+ && !ALWAYS_ENFORCE_PREDICTIVE_BACK) {
Log.w("OnBackInvokedCallback",
"OnBackInvokedCallback is not enabled for the application."
+ "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the"
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index 20ff83f..f885a7e 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
@@ -34,6 +35,7 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
/**
* An implementation of {@link ImeTracing} for the system_server process.
@@ -139,18 +141,30 @@
private void writeTracesToFilesLocked() {
try {
+ long timeOffsetNs =
+ TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+ - SystemClock.elapsedRealtimeNanos();
+
ProtoOutputStream clientsProto = new ProtoOutputStream();
clientsProto.write(InputMethodClientsTraceFileProto.MAGIC_NUMBER,
MAGIC_NUMBER_CLIENTS_VALUE);
+ clientsProto.write(InputMethodClientsTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
+ timeOffsetNs);
mBufferClients.writeTraceToFile(mTraceFileClients, clientsProto);
ProtoOutputStream imsProto = new ProtoOutputStream();
- imsProto.write(InputMethodServiceTraceFileProto.MAGIC_NUMBER, MAGIC_NUMBER_IMS_VALUE);
+ imsProto.write(InputMethodServiceTraceFileProto.MAGIC_NUMBER,
+ MAGIC_NUMBER_IMS_VALUE);
+ imsProto.write(InputMethodServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
+ timeOffsetNs);
mBufferIms.writeTraceToFile(mTraceFileIms, imsProto);
ProtoOutputStream immsProto = new ProtoOutputStream();
immsProto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER,
MAGIC_NUMBER_IMMS_VALUE);
+ immsProto.write(
+ InputMethodManagerServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS,
+ timeOffsetNs);
mBufferImms.writeTraceToFile(mTraceFileImms, immsProto);
resetBuffers();
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
new file mode 100644
index 0000000..a8885f9
--- /dev/null
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.SystemClock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A timeout that has triggered on the system.
+ *
+ * @hide
+ */
+public class TimeoutRecord {
+ /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+ @IntDef(value = {
+ TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW,
+ TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE,
+ TimeoutKind.BROADCAST_RECEIVER,
+ TimeoutKind.SERVICE_START,
+ TimeoutKind.SERVICE_EXEC,
+ TimeoutKind.CONTENT_PROVIDER,
+ TimeoutKind.APP_REGISTERED})
+
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface TimeoutKind {
+ int INPUT_DISPATCH_NO_FOCUSED_WINDOW = 1;
+ int INPUT_DISPATCH_WINDOW_UNRESPONSIVE = 2;
+ int BROADCAST_RECEIVER = 3;
+ int SERVICE_START = 4;
+ int SERVICE_EXEC = 5;
+ int CONTENT_PROVIDER = 6;
+ int APP_REGISTERED = 7;
+ }
+
+ /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
+ @TimeoutKind
+ public final int mKind;
+
+ /** Reason for the timeout. */
+ public final String mReason;
+
+ /** System uptime in millis when the timeout was triggered. */
+ public final long mEndUptimeMillis;
+
+ /**
+ * Was the end timestamp taken right after the timeout triggered, before any potentially
+ * expensive operations such as taking locks?
+ */
+ public final boolean mEndTakenBeforeLocks;
+
+ private TimeoutRecord(@TimeoutKind int kind, @NonNull String reason, long endUptimeMillis,
+ boolean endTakenBeforeLocks) {
+ this.mKind = kind;
+ this.mReason = reason;
+ this.mEndUptimeMillis = endUptimeMillis;
+ this.mEndTakenBeforeLocks = endTakenBeforeLocks;
+ }
+
+ private static TimeoutRecord endingNow(@TimeoutKind int kind, String reason) {
+ long endUptimeMillis = SystemClock.uptimeMillis();
+ return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ true);
+ }
+
+ private static TimeoutRecord endingApproximatelyNow(@TimeoutKind int kind, String reason) {
+ long endUptimeMillis = SystemClock.uptimeMillis();
+ return new TimeoutRecord(kind, reason, endUptimeMillis, /* endTakenBeforeLocks */ false);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ }
+
+ /** Record for an input dispatch no focused window timeout */
+ @NonNull
+ public static TimeoutRecord forInputDispatchNoFocusedWindow(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW, reason);
+ }
+
+ /** Record for an input dispatch window unresponsive timeout. */
+ @NonNull
+ public static TimeoutRecord forInputDispatchWindowUnresponsive(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE, reason);
+ }
+
+ /** Record for a service exec timeout. */
+ @NonNull
+ public static TimeoutRecord forServiceExec(@NonNull String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.SERVICE_EXEC, reason);
+ }
+
+ /** Record for a service start timeout. */
+ @NonNull
+ public static TimeoutRecord forServiceStartWithEndTime(@NonNull String reason,
+ long endUptimeMillis) {
+ return new TimeoutRecord(TimeoutKind.SERVICE_START, reason,
+ endUptimeMillis, /* endTakenBeforeLocks */ true);
+ }
+
+ /** Record for a content provider timeout. */
+ @NonNull
+ public static TimeoutRecord forContentProvider(@NonNull String reason) {
+ return TimeoutRecord.endingApproximatelyNow(TimeoutKind.CONTENT_PROVIDER, reason);
+ }
+
+ /** Record for an app registered timeout. */
+ @NonNull
+ public static TimeoutRecord forApp(@NonNull String reason) {
+ return TimeoutRecord.endingApproximatelyNow(TimeoutKind.APP_REGISTERED, reason);
+ }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 7f36c79..0e8dc07 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -83,7 +83,7 @@
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
- WM_DEBUG_CONTENT_RECORDING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ WM_DEBUG_CONTENT_RECORDING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index b9243ec..9474f6f 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,6 +1,7 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,7 +28,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
-import android.view.WindowManager;
import android.view.WindowManager.ScreenshotSource;
import android.view.WindowManager.ScreenshotType;
@@ -42,10 +42,15 @@
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
/**
- * Describes a screenshot request (to make it easier to pass data through to the handler).
+ * Describes a screenshot request.
*/
public static class ScreenshotRequest implements Parcelable {
+ @ScreenshotType
+ private final int mType;
+
+ @ScreenshotSource
private final int mSource;
+
private final Bundle mBitmapBundle;
private final Rect mBoundsInScreen;
private final Insets mInsets;
@@ -53,20 +58,27 @@
private final int mUserId;
private final ComponentName mTopComponent;
- @VisibleForTesting
- public ScreenshotRequest(int source) {
- mSource = source;
- mBitmapBundle = null;
- mBoundsInScreen = null;
- mInsets = null;
- mTaskId = -1;
- mUserId = -1;
- mTopComponent = null;
+
+ public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
+ this(type, source, /* topComponent */ null);
}
- @VisibleForTesting
- public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
- Insets insets, int taskId, int userId, ComponentName topComponent) {
+ public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+ ComponentName topComponent) {
+ this(type,
+ source,
+ /* bitmapBundle*/ null,
+ /* boundsInScreen */ null,
+ /* insets */ null,
+ /* taskId */ -1,
+ /* userId */ -1,
+ topComponent);
+ }
+
+ public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
+ Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
+ ComponentName topComponent) {
+ mType = type;
mSource = source;
mBitmapBundle = bitmapBundle;
mBoundsInScreen = boundsInScreen;
@@ -77,6 +89,7 @@
}
ScreenshotRequest(Parcel in) {
+ mType = in.readInt();
mSource = in.readInt();
if (in.readInt() == 1) {
mBitmapBundle = in.readBundle(getClass().getClassLoader());
@@ -96,6 +109,12 @@
}
}
+ @ScreenshotType
+ public int getType() {
+ return mType;
+ }
+
+ @ScreenshotSource
public int getSource() {
return mSource;
}
@@ -131,6 +150,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
dest.writeInt(mSource);
if (mBitmapBundle == null) {
dest.writeInt(0);
@@ -208,8 +228,7 @@
* Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
*
* <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
- * this
- * Bitmap on to any other source.
+ * this Bitmap on to any other source.
*
* @param bundle containing the bitmap
* @return a hardware Bitmap
@@ -261,16 +280,16 @@
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param type The type of screenshot, defined by {@link ScreenshotType}
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
* @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
* null if no screenshot was saved
*/
- public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
@NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
- takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+ takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
completionConsumer);
}
@@ -280,7 +299,7 @@
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param type The type of screenshot, defined by {@link ScreenshotType}
* @param source The source of the screenshot request, defined by {@link ScreenshotSource}
* @param handler used to process messages received from the screenshot service
* @param timeoutMs time limit for processing, intended only for testing
@@ -288,10 +307,10 @@
* null if no screenshot was saved
*/
@VisibleForTesting
- public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
@NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
- takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
+ takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
}
/**
@@ -312,14 +331,12 @@
@NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
@ScreenshotSource int source, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
- boundsInScreen, insets, taskId, userId, topComponent);
- takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
- SCREENSHOT_TIMEOUT_MS,
- completionConsumer);
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
+ takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
}
- private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+ private void takeScreenshot(@NonNull Handler handler,
ScreenshotRequest screenshotRequest, long timeoutMs,
@Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
@@ -337,7 +354,7 @@
}
};
- Message msg = Message.obtain(null, screenshotType, screenshotRequest);
+ Message msg = Message.obtain(null, 0, screenshotRequest);
Handler h = new Handler(handler.getLooper()) {
@Override
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 22f84b6..9ae1630 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1116,12 +1116,6 @@
return array;
}
-static jlong nativeGetPrimaryPhysicalDisplayId(JNIEnv* env, jclass clazz) {
- PhysicalDisplayId displayId;
- SurfaceComposerClient::getPrimaryPhysicalDisplayId(&displayId);
- return static_cast<jlong>(displayId.value);
-}
-
static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong physicalDisplayId) {
const auto id = DisplayId::fromValue<PhysicalDisplayId>(physicalDisplayId);
if (!id) return nullptr;
@@ -2210,8 +2204,6 @@
(void*)nativeSetDefaultFrameRateCompatibility},
{"nativeGetPhysicalDisplayIds", "()[J",
(void*)nativeGetPhysicalDisplayIds },
- {"nativeGetPrimaryPhysicalDisplayId", "()J",
- (void*)nativeGetPrimaryPhysicalDisplayId },
{"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
(void*)nativeGetPhysicalDisplayToken },
{"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
diff --git a/core/proto/android/os/batteryusagestats.proto b/core/proto/android/os/batteryusagestats.proto
index 856bc83..2b74220 100644
--- a/core/proto/android/os/batteryusagestats.proto
+++ b/core/proto/android/os/batteryusagestats.proto
@@ -102,4 +102,21 @@
// Total amount of time battery was discharging during the reported session
optional int64 discharge_duration_millis = 7;
+
+ // Notes the power model used for a power component.
+ message PowerComponentModel {
+ // Holds android.os.PowerComponentEnum, or custom component value between 1000 and 9999.
+ optional int32 component = 1;
+
+ enum PowerModel {
+ UNDEFINED = 0;
+ POWER_PROFILE = 1;
+ MEASURED_ENERGY = 2;
+ }
+
+ optional PowerModel power_model = 2;
+ }
+
+ // The power model used for each power component.
+ repeated PowerComponentModel component_models = 8;
}
diff --git a/core/proto/android/os/processstarttime.proto b/core/proto/android/os/processstarttime.proto
deleted file mode 100644
index d0f8bae..0000000
--- a/core/proto/android/os/processstarttime.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-package android.os;
-
-option java_multiple_files = true;
-
-// This message is used for statsd logging and should be kept in sync with
-// frameworks/proto_logging/stats/atoms.proto
-/**
- * Logs information about process start time.
- *
- * Logged from:
- * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message ProcessStartTime {
- // The uid of the ProcessRecord.
- optional int32 uid = 1;
-
- // The process pid.
- optional int32 pid = 2;
-
- // The process name.
- // Usually package name, "system" for system server.
- // Provided by ActivityManagerService.
- optional string process_name = 3;
-
- enum StartType {
- UNKNOWN = 0;
- WARM = 1;
- HOT = 2;
- COLD = 3;
- }
-
- // The start type.
- optional StartType type = 4;
-
- // The elapsed realtime at the start of the process.
- optional int64 process_start_time_millis = 5;
-
- // Number of milliseconds it takes to reach bind application.
- optional int32 bind_application_delay_millis = 6;
-
- // Number of milliseconds it takes to finish start of the process.
- optional int32 process_start_delay_millis = 7;
-
- // hostingType field in ProcessRecord, the component type such as "activity",
- // "service", "content provider", "broadcast" or other strings.
- optional string hosting_type = 8;
-
- // hostingNameStr field in ProcessRecord. The component class name that runs
- // in this process.
- optional string hosting_name = 9;
-
- // Broadcast action name.
- optional string broadcast_action_name = 10;
-
- enum HostingTypeId {
- HOSTING_TYPE_UNKNOWN = 0;
- HOSTING_TYPE_ACTIVITY = 1;
- HOSTING_TYPE_ADDED_APPLICATION = 2;
- HOSTING_TYPE_BACKUP = 3;
- HOSTING_TYPE_BROADCAST = 4;
- HOSTING_TYPE_CONTENT_PROVIDER = 5;
- HOSTING_TYPE_LINK_FAIL = 6;
- HOSTING_TYPE_ON_HOLD = 7;
- HOSTING_TYPE_NEXT_ACTIVITY = 8;
- HOSTING_TYPE_NEXT_TOP_ACTIVITY = 9;
- HOSTING_TYPE_RESTART = 10;
- HOSTING_TYPE_SERVICE = 11;
- HOSTING_TYPE_SYSTEM = 12;
- HOSTING_TYPE_TOP_ACTIVITY = 13;
- HOSTING_TYPE_EMPTY = 14;
- }
-
- optional HostingTypeId hosting_type_id = 11;
-}
-
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 285258a..322354b 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -88,6 +88,7 @@
optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/server/accessibilitytrace.proto b/core/proto/android/server/accessibilitytrace.proto
index 0ce39ab..6b77be3 100644
--- a/core/proto/android/server/accessibilitytrace.proto
+++ b/core/proto/android/server/accessibilitytrace.proto
@@ -39,6 +39,10 @@
optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
repeated AccessibilityTraceProto entry = 2;
+
+ /* offset between real-time clock and elapsed time clock in nanoseconds.
+ Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+ optional fixed64 real_to_elapsed_time_offset_nanos = 3;
}
/* one accessibility trace entry. */
diff --git a/core/proto/android/server/windowmanagertrace.proto b/core/proto/android/server/windowmanagertrace.proto
index f502961..257f79b 100644
--- a/core/proto/android/server/windowmanagertrace.proto
+++ b/core/proto/android/server/windowmanagertrace.proto
@@ -38,6 +38,10 @@
optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
repeated WindowManagerTraceProto entry = 2;
+
+ /* offset between real-time clock and elapsed time clock in nanoseconds.
+ Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+ optional fixed64 real_to_elapsed_time_offset_nanos = 3;
}
/* one window manager trace entry. */
diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
index 8e4377c..a4281a7 100644
--- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
+++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
@@ -50,6 +50,10 @@
in MagicNumber */
optional fixed64 magic_number = 1;
repeated InputMethodClientsTraceProto entry = 2;
+
+ /* offset between real-time clock and elapsed time clock in nanoseconds.
+ Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+ optional fixed64 real_to_elapsed_time_offset_nanos = 3;
}
/* One dump entry for clients that use InputMethod. */
@@ -96,6 +100,10 @@
in MagicNumber */
optional fixed64 magic_number = 1;
repeated InputMethodServiceTraceProto entry = 2;
+
+ /* offset between real-time clock and elapsed time clock in nanoseconds.
+ Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+ optional fixed64 real_to_elapsed_time_offset_nanos = 3;
}
/* One dump entry for InputMethodService. */
@@ -129,6 +137,10 @@
in MagicNumber */
optional fixed64 magic_number = 1;
repeated InputMethodManagerServiceTraceProto entry = 2;
+
+ /* offset between real-time clock and elapsed time clock in nanoseconds.
+ Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */
+ optional fixed64 real_to_elapsed_time_offset_nanos = 3;
}
/* One dump entry for InputMethodManagerService. */
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
new file mode 100644
index 0000000..58b8cc9
--- /dev/null
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="350dp"
+ android:layout_gravity="center"
+ android:theme="?attr/alertDialogTheme">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/fp_power_button_enrollment_title"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:paddingLeft="20dp"/>
+ <Space
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ <Button
+ android:id="@+id/turn_off_screen"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/fp_power_button_enrollment_button_text"
+ android:paddingRight="20dp"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:maxLines="1"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 08471c3..d43a6c5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3075,6 +3075,20 @@
<enum name="noHideDescendants" value="4" />
</attr>
+ <!-- Describes whether this view should allow interactions from AccessibilityServices only
+ if the service sets the isAccessibilityTool property. -->
+ <attr name="accessibilityDataPrivate" format="integer">
+ <!-- The system determines whether the view's accessibility data is private
+ - default (recommended). -->
+ <enum name="auto" value="0" />
+ <!-- Allow interactions from AccessibilityServices only if the service sets the
+ isAccessibilityTool property. -->
+ <enum name="yes" value="1" />
+ <!-- Allow interactions from all AccessibilityServices, regardless of their
+ isAccessibilityTool property. -->
+ <enum name="no" value="2" />
+ </attr>
+
<!-- Indicates to accessibility services whether the user should be notified when
this view changes. -->
<attr name="accessibilityLiveRegion" format="integer">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d176a1d..9faf5e8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2004,6 +2004,9 @@
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
+ <!-- Flag indicating current media Output Switcher version. -->
+ <integer name="config_mediaOutputSwitchDialogVersion">1</integer>
+
<!-- Flag indicating that an outbound call must have a call capable phone account
that has declared it can process the call's handle. -->
<bool name="config_requireCallCapableAccountForHandle">false</bool>
@@ -3492,9 +3495,9 @@
(for side fingerprint) -->
<integer name="config_sidefpsPostAuthDowntime">400</integer>
- <!-- The time (in millis) that a finger tap will wait for a power button
- before dismissing the power dialog during enrollment(for side fingerprint) -->
- <integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
+ <!-- The time (in millis) the clickable toast dialog will last until
+ automatically dismissing. This is currently used in SideFpsEventHandler -->
+ <integer name="config_sideFpsToastTimeout">3000</integer>
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
It declares the package name of VoiceInteractionService that should be started. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 11c245b..ad7d03e 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -114,6 +114,7 @@
<public name="handwritingBoundsOffsetTop" />
<public name="handwritingBoundsOffsetRight" />
<public name="handwritingBoundsOffsetBottom" />
+ <public name="accessibilityDataPrivate" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3f2607..d3d0493 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3561,21 +3561,17 @@
<!-- [CHAR LIMIT=NONE] Message to show in upgrading dialog when the bulk of the upgrade work is done. -->
<string name="android_upgrading_complete">Finishing boot.</string>
- <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
- is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_title">Continue setup?</string>
-
<!-- [CHAR LIMIT=NONE] Message of dialog shown to confirm device going to sleep if the power
button is pressed during fingerprint enrollment. -->
<string name="fp_power_button_enrollment_message">You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint.</string>
+ <!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
+ is pressed during fingerprint enrollment. -->
+ <string name="fp_power_button_enrollment_title">Tap to turn off screen</string>
+
<!-- [CHAR LIMIT=20] Positive button of dialog shown to confirm device going to sleep if the
power button is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_positive_button">Turn off screen</string>
-
- <!-- [CHAR LIMIT=20] Negative button of dialog shown to confirm device going to sleep if the
- power button is pressed during fingerprint enrollment. -->
- <string name="fp_power_button_enrollment_negative_button">Continue setup</string>
+ <string name="fp_power_button_enrollment_button_text">Turn off screen</string>
<!-- [CHAR LIMIT=40] Title of dialog shown to confirm device going to sleep if the power button
is pressed during biometric prompt when a side fingerprint sensor is present. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e6ae7b1..51712ff 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1849,8 +1849,7 @@
<java-symbol type="string" name="fp_power_button_bp_negative_button" />
<java-symbol type="string" name="fp_power_button_enrollment_title" />
<java-symbol type="string" name="fp_power_button_enrollment_message" />
- <java-symbol type="string" name="fp_power_button_enrollment_positive_button" />
- <java-symbol type="string" name="fp_power_button_enrollment_negative_button" />
+ <java-symbol type="string" name="fp_power_button_enrollment_button_text" />
<java-symbol type="string" name="global_actions" />
<java-symbol type="string" name="global_action_power_off" />
<java-symbol type="string" name="global_action_power_options" />
@@ -2627,7 +2626,11 @@
<java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
<java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
- <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sideFpsToastTimeout"/>
+
+ <!-- Clickable toast used during sidefps enrollment -->
+ <java-symbol type="layout" name="side_fps_toast" />
+ <java-symbol type="id" name="turn_off_screen" />
<!-- Face authentication messages -->
<java-symbol type="string" name="face_recalibrate_notification_name" />
@@ -4706,6 +4709,8 @@
<java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" />
+ <java-symbol type="integer" name="config_mediaOutputSwitchDialogVersion" />
+
<!-- List of shared library packages that should be loaded by the classloader after the
code and resources provided by applications. -->
<java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index 2ce3054..f82523c 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
import android.os.UidBatteryConsumer;
@@ -69,10 +70,17 @@
assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis);
assertEquals(3, proto.deviceBatteryConsumer.powerComponents.length); // Only 3 are non-empty
+
+ final AggregateBatteryConsumer abc = bus.getAggregateBatteryConsumer(
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
assertSameBatteryConsumer("For deviceBatteryConsumer",
bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE),
proto.deviceBatteryConsumer);
+ for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
+ assertPowerComponentModel(i, abc.getPowerModel(i), proto);
+ }
+
// Now for the UidBatteryConsumers.
final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
@@ -196,6 +204,34 @@
}
}
+ /**
+ * Validates the PowerComponentModel object that matches powerComponent.
+ */
+ private void assertPowerComponentModel(int powerComponent,
+ @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) {
+ boolean found = false;
+ for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel :
+ proto.componentModels) {
+ if (powerComponentModel.component == powerComponent) {
+ if (found) {
+ fail("Power component " + BatteryConsumer.powerComponentIdToString(
+ powerComponent) + " found multiple times in the proto");
+ }
+ found = true;
+ final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel);
+ assertEquals(expectedPowerModel, powerComponentModel.powerModel);
+ }
+ }
+ if (!found) {
+ final int model = BatteryConsumer.powerModelToProtoEnum(powerModel);
+ assertEquals(
+ "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent)
+ + " was not found in the proto but has a defined power model.",
+ BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED,
+ model);
+ }
+ }
+
/** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
private long convertMahToDc(double powerMah) {
return (long) (powerMah * 36 + 0.5);
@@ -259,11 +295,14 @@
builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setConsumedPower(30000)
.setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 20100)
+ BatteryConsumer.POWER_COMPONENT_CPU, 20100,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
.setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_AUDIO, 0) // Empty
+ BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
.setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CAMERA, 20150)
+ BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
+ BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
.setUsageDurationMillis(
@@ -275,7 +314,8 @@
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 10100)
+ BatteryConsumer.POWER_COMPONENT_CPU, 10100,
+ BatteryConsumer.POWER_MODEL_POWER_PROFILE)
.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
@@ -285,7 +325,7 @@
@Test
public void testLargeAtomTruncated() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[0]);
+ new BatteryUsageStats.Builder(new String[0], true, false);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
// significantly larger than 50 Kb
for (int i = 0; i < 3000; i++) {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 2054b4f..8cf118c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,8 +18,10 @@
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
import static junit.framework.Assert.assertEquals;
@@ -28,6 +30,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -75,6 +78,7 @@
private boolean mRemoveSurfaceCalled = false;
private InsetsController mController;
private InsetsState mState;
+ private ViewRootImpl mViewRoot;
@Before
public void setup() {
@@ -86,10 +90,9 @@
instrumentation.runOnMainSync(() -> {
final Context context = instrumentation.getTargetContext();
// cannot mock ViewRootImpl since it's final.
- final ViewRootImpl viewRootImpl = new ViewRootImpl(context,
- context.getDisplayNoVerify());
+ mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify());
try {
- viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+ mViewRoot.setView(new TextView(context), new LayoutParams(), null);
} catch (BadTokenException e) {
// activity isn't running, lets ignore BadTokenException.
}
@@ -97,7 +100,7 @@
mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
mState.addSource(mSpyInsetsSource);
- mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
+ mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
() -> mMockTransaction, mController) {
@Override
@@ -207,4 +210,40 @@
});
}
+
+ @Test
+ public void testWontUpdateImeLeashVisibility_whenAnimation() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsState state = new InsetsState();
+ ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
+ InsetsController insetsController = new InsetsController(host, (controller, type) -> {
+ if (type == ITYPE_IME) {
+ return new InsetsSourceConsumer(ITYPE_IME, state,
+ () -> mMockTransaction, controller) {
+ @Override
+ public int requestShow(boolean fromController) {
+ return SHOW_IMMEDIATELY;
+ }
+ };
+ }
+ return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
+ controller);
+ }, host.getHandler());
+ InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
+
+ // Initial IME insets source control with its leash.
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+ reset(mMockTransaction);
+
+ // Verify when the app requests controlling show IME animation, the IME leash
+ // visibility won't be updated when the consumer received the same leash in setControl.
+ insetsController.controlWindowInsetsAnimation(ime(), 0L,
+ null /* interpolator */, null /* cancellationSignal */, null /* listener */);
+ assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+ verify(mMockTransaction, never()).show(mLeash);
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowParamsTest.java b/core/tests/coretests/src/android/view/WindowParamsTest.java
new file mode 100644
index 0000000..49d4872
--- /dev/null
+++ b/core/tests/coretests/src/android/view/WindowParamsTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 android.view;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class WindowParamsTest {
+
+ @Test
+ public void testParamsForRotation() {
+ final WindowManager.LayoutParams paramsA = new WindowManager.LayoutParams();
+ initParamsForRotation(paramsA);
+ final Parcel parcel = Parcel.obtain();
+ paramsA.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final WindowManager.LayoutParams paramsB = new WindowManager.LayoutParams(parcel);
+ assertEquals(0, paramsA.copyFrom(paramsB));
+
+ for (int i = 1; i <= 12; i++) {
+ initParamsForRotation(paramsA);
+ changeField(i, paramsA.paramsForRotation[0]);
+ assertEquals("Change not found for case " + i,
+ WindowManager.LayoutParams.LAYOUT_CHANGED, paramsA.copyFrom(paramsB));
+ }
+
+ parcel.recycle();
+ }
+
+ private static void initParamsForRotation(WindowManager.LayoutParams params) {
+ params.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int i = 0; i < 4; i++) {
+ params.paramsForRotation[i] = new WindowManager.LayoutParams();
+ }
+ }
+
+ private static void changeField(int fieldCase, WindowManager.LayoutParams params) {
+ switch (fieldCase) {
+ case 1:
+ params.width++;
+ break;
+ case 2:
+ params.height++;
+ break;
+ case 3:
+ params.x++;
+ break;
+ case 4:
+ params.y++;
+ break;
+ case 5:
+ params.horizontalMargin++;
+ break;
+ case 6:
+ params.verticalMargin++;
+ break;
+ case 7:
+ params.layoutInDisplayCutoutMode++;
+ break;
+ case 8:
+ params.gravity++;
+ break;
+ case 9:
+ params.providedInsets = new InsetsFrameProvider[0];
+ break;
+ case 10:
+ params.setFitInsetsTypes(0);
+ break;
+ case 11:
+ params.setFitInsetsSides(0);
+ break;
+ case 12:
+ params.setFitInsetsIgnoringVisibility(!params.isFitInsetsIgnoringVisibility());
+ break;
+ }
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 505d2a3..03d22ac 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -13,12 +13,24 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-2123789565": {
+ "message": "Found no matching mirror display for id=%d for DEFAULT_DISPLAY. Nothing to mirror.",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-2121056984": {
"message": "%s",
"level": "WARN",
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "-2113780196": {
+ "message": "Successfully created a ContentRecordingSession for displayId=%d to mirror content from displayId=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-2111539867": {
"message": "remove IME snapshot, caller=%s",
"level": "INFO",
@@ -1111,6 +1123,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-1018968224": {
+ "message": "Recorded task is removed, so stop recording on display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1016578046": {
"message": "Moving to %s Relaunching %s callers=%s",
"level": "INFO",
@@ -1303,6 +1321,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-838378223": {
+ "message": "Attempting to mirror self on %d",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-814760297": {
"message": "Looking for task of %s in %s",
"level": "DEBUG",
@@ -1411,6 +1435,12 @@
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
},
+ "-729864558": {
+ "message": "Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-729530161": {
"message": "Moving to DESTROYED: %s (no app)",
"level": "VERBOSE",
@@ -2251,6 +2281,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "96494268": {
+ "message": "Stop MediaProjection on virtual display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"100936473": {
"message": "Wallpaper animation!",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 7cc22d7..0e67f1f 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -1046,14 +1046,39 @@
}
/** @hide */
- public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) {
- if (srcRect == null) {
- // Empty rect means entire surface
- return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap.getNativeInstance());
- } else {
- return nCopySurfaceInto(surface, srcRect.left, srcRect.top,
- srcRect.right, srcRect.bottom, bitmap.getNativeInstance());
+ public abstract static class CopyRequest {
+ protected Bitmap mDestinationBitmap;
+ final Rect mSrcRect;
+
+ protected CopyRequest(Rect srcRect, Bitmap destinationBitmap) {
+ mDestinationBitmap = destinationBitmap;
+ if (srcRect != null) {
+ mSrcRect = srcRect;
+ } else {
+ mSrcRect = new Rect();
+ }
}
+
+ /**
+ * Retrieve the bitmap in which to store the result of the copy request
+ */
+ public long getDestinationBitmap(int srcWidth, int srcHeight) {
+ if (mDestinationBitmap == null) {
+ mDestinationBitmap =
+ Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
+ }
+ return mDestinationBitmap.getNativeInstance();
+ }
+
+ /** Called when the copy is completed */
+ public abstract void onCopyFinished(int result);
+ }
+
+ /** @hide */
+ public static void copySurfaceInto(Surface surface, CopyRequest copyRequest) {
+ final Rect srcRect = copyRequest.mSrcRect;
+ nCopySurfaceInto(surface, srcRect.left, srcRect.top, srcRect.right, srcRect.bottom,
+ copyRequest);
}
/**
@@ -1464,8 +1489,8 @@
private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
- private static native int nCopySurfaceInto(Surface surface,
- int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
+ private static native void nCopySurfaceInto(Surface surface,
+ int srcLeft, int srcTop, int srcRight, int srcBottom, CopyRequest request);
private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height);
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 2797a4d..0f82c8f 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -20,12 +20,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
import android.graphics.Rect;
import android.os.Handler;
import android.view.ViewTreeObserver.OnDrawListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides a mechanisms to issue pixel copy requests to allow for copy
@@ -183,12 +186,10 @@
if (srcRect != null && srcRect.isEmpty()) {
throw new IllegalArgumentException("sourceRect is empty");
}
- // TODO: Make this actually async and fast and cool and stuff
- int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest);
- listenerThread.post(new Runnable() {
+ HardwareRenderer.copySurfaceInto(source, new HardwareRenderer.CopyRequest(srcRect, dest) {
@Override
- public void run() {
- listener.onPixelCopyFinished(result);
+ public void onCopyFinished(int result) {
+ listenerThread.post(() -> listener.onPixelCopyFinished(result));
}
});
}
@@ -255,30 +256,10 @@
@NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener,
@NonNull Handler listenerThread) {
validateBitmapDest(dest);
- if (source == null) {
- throw new IllegalArgumentException("source is null");
- }
- if (source.peekDecorView() == null) {
- throw new IllegalArgumentException(
- "Only able to copy windows with decor views");
- }
- Surface surface = null;
- final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
- if (root != null) {
- surface = root.mSurface;
- final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
- if (srcRect == null) {
- srcRect = new Rect(surfaceInsets.left, surfaceInsets.top,
- root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
- } else {
- srcRect.offset(surfaceInsets.left, surfaceInsets.top);
- }
- }
- if (surface == null || !surface.isValid()) {
- throw new IllegalArgumentException(
- "Window doesn't have a backing surface!");
- }
- request(surface, srcRect, dest, listener, listenerThread);
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ request(surface, adjustSourceRectForInsets(insets, srcRect), dest, listener,
+ listenerThread);
}
private static void validateBitmapDest(Bitmap bitmap) {
@@ -294,5 +275,235 @@
}
}
+ private static Surface sourceForWindow(Window source, Rect outInsets) {
+ if (source == null) {
+ throw new IllegalArgumentException("source is null");
+ }
+ if (source.peekDecorView() == null) {
+ throw new IllegalArgumentException(
+ "Only able to copy windows with decor views");
+ }
+ Surface surface = null;
+ final ViewRootImpl root = source.peekDecorView().getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets;
+ outInsets.set(surfaceInsets.left, surfaceInsets.top,
+ root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top);
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ return surface;
+ }
+
+ private static Rect adjustSourceRectForInsets(Rect insets, Rect srcRect) {
+ if (srcRect == null) {
+ return insets;
+ }
+ if (insets != null) {
+ srcRect.offset(insets.left, insets.top);
+ }
+ return srcRect;
+ }
+
+ /**
+ * Contains the result of a PixelCopy request
+ */
+ public static final class CopyResult {
+ private int mStatus;
+ private Bitmap mBitmap;
+
+ private CopyResult(@CopyResultStatus int status, Bitmap bitmap) {
+ mStatus = status;
+ mBitmap = bitmap;
+ }
+
+ /**
+ * Returns the {@link CopyResultStatus} of the copy request.
+ */
+ public @CopyResultStatus int getStatus() {
+ return mStatus;
+ }
+
+ private void validateStatus() {
+ if (mStatus != SUCCESS) {
+ throw new IllegalStateException("Copy request didn't succeed, status = " + mStatus);
+ }
+ }
+
+ /**
+ * If the PixelCopy {@link Request} was given a destination bitmap with
+ * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same
+ * as the one given. If no destination bitmap was provided, then this
+ * will contain the automatically allocated Bitmap to hold the result.
+ *
+ * @return the Bitmap the copy request was stored in.
+ * @throws IllegalStateException if {@link #getStatus()} is not SUCCESS
+ */
+ public @NonNull Bitmap getBitmap() {
+ validateStatus();
+ return mBitmap;
+ }
+ }
+
+ /**
+ * A builder to create the complete PixelCopy request, which is then executed by calling
+ * {@link #request()}
+ */
+ public static final class Request {
+ private Request(Surface source, Rect sourceInsets, Executor listenerThread,
+ Consumer<CopyResult> listener) {
+ this.mSource = source;
+ this.mSourceInsets = sourceInsets;
+ this.mListenerThread = listenerThread;
+ this.mListener = listener;
+ }
+
+ private final Surface mSource;
+ private final Consumer<CopyResult> mListener;
+ private final Executor mListenerThread;
+ private final Rect mSourceInsets;
+ private Rect mSrcRect;
+ private Bitmap mDest;
+
+ /**
+ * Sets the region of the source to copy from. By default, the entire source is copied to
+ * the output. If only a subset of the source is necessary to be copied, specifying a
+ * srcRect will improve performance by reducing
+ * the amount of data being copied.
+ *
+ * @param srcRect The area of the source to read from. Null or empty will be treated to
+ * mean the entire source
+ * @return this
+ */
+ public @NonNull Request setSourceRect(@Nullable Rect srcRect) {
+ this.mSrcRect = srcRect;
+ return this;
+ }
+
+ /**
+ * Specifies the output bitmap in which to store the result. By default, a Bitmap of format
+ * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that
+ * of the {@link #setSourceRect(Rect) source area} will be created to place the result.
+ *
+ * @param destination The bitmap to store the result, or null to have a bitmap
+ * automatically created of the appropriate size. If not null, must not
+ * be {@link Bitmap#isRecycled() recycled} and must be
+ * {@link Bitmap#isMutable() mutable}.
+ * @return this
+ */
+ public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) {
+ if (destination != null) {
+ validateBitmapDest(destination);
+ }
+ this.mDest = destination;
+ return this;
+ }
+
+ /**
+ * Executes the request.
+ */
+ public void request() {
+ if (!mSource.isValid()) {
+ mListenerThread.execute(() -> mListener.accept(
+ new CopyResult(ERROR_SOURCE_INVALID, null)));
+ return;
+ }
+ HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
+ adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) {
+ @Override
+ public void onCopyFinished(int result) {
+ mListenerThread.execute(() -> mListener.accept(
+ new CopyResult(result, mDestinationBitmap)));
+ }
+ });
+ }
+ }
+
+ /**
+ * Creates a PixelCopy request for the given {@link Window}
+ * @param source The Window to copy from
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofWindow(@NonNull Window source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<CopyResult> listener) {
+ final Rect insets = new Rect();
+ final Surface surface = sourceForWindow(source, insets);
+ return new Request(surface, insets, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * attached to.
+ *
+ * Note that this copy request is not cropped to the area the View occupies by default. If that
+ * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
+ * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
+ *
+ * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
+ * will be used to retrieve the window to copy from.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofWindow(@NonNull View source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<CopyResult> listener) {
+ if (source == null || !source.isAttachedToWindow()) {
+ throw new IllegalArgumentException(
+ "View must not be null & must be attached to window");
+ }
+ final Rect insets = new Rect();
+ Surface surface = null;
+ final ViewRootImpl root = source.getViewRootImpl();
+ if (root != null) {
+ surface = root.mSurface;
+ insets.set(root.mWindowAttributes.surfaceInsets);
+ }
+ if (surface == null || !surface.isValid()) {
+ throw new IllegalArgumentException(
+ "Window doesn't have a backing surface!");
+ }
+ return new Request(surface, insets, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the given {@link Surface}
+ *
+ * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofSurface(@NonNull Surface source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<CopyResult> listener) {
+ if (source == null || !source.isValid()) {
+ throw new IllegalArgumentException("Source must not be null & must be valid");
+ }
+ return new Request(source, null, callbackExecutor, listener);
+ }
+
+ /**
+ * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * given {@link SurfaceView}
+ *
+ * @param source The SurfaceView to copy from. The backing surface must be
+ * {@link Surface#isValid() valid}
+ * @param callbackExecutor The executor to run the callback on
+ * @param listener The callback for when the copy request is completed
+ * @return A {@link Request} builder to set the optional params & execute the request
+ */
+ public static @NonNull Request ofSurface(@NonNull SurfaceView source,
+ @NonNull Executor callbackExecutor,
+ @NonNull Consumer<CopyResult> listener) {
+ return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+ }
+
private PixelCopy() {}
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index f54ab08..918e514 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml
new file mode 100644
index 0000000..0bcaa53
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.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"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index a112f19..d183e42 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -22,6 +22,17 @@
android:gravity="end"
android:background="@drawable/decor_caption_title">
<Button
+ android:id="@+id/minimize_window"
+ android:visibility="gone"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="top|end"
+ android:contentDescription="@string/maximize_button_text"
+ android:background="@drawable/decor_minimize_button_dark"
+ android:duplicateParentState="true"/>
+ <Button
android:id="@+id/maximize_window"
android:layout_width="32dp"
android:layout_height="32dp"
@@ -42,4 +53,3 @@
android:background="@drawable/decor_close_button_dark"
android:duplicateParentState="true"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
-
diff --git a/libs/WindowManager/Shell/res/values-es/strings_tv.xml b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
index 7993e03..75db421 100644
--- a/libs/WindowManager/Shell/res/values-es/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings_tv.xml
@@ -23,7 +23,7 @@
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
<string name="pip_move" msgid="158770205886688553">"Mover"</string>
<string name="pip_expand" msgid="1051966011679297308">"Mostrar"</string>
- <string name="pip_collapse" msgid="3903295106641385962">"Ocultar"</string>
+ <string name="pip_collapse" msgid="3903295106641385962">"Contraer"</string>
<string name="pip_edu_text" msgid="3672999496647508701">" Pulsa dos veces "<annotation icon="home_icon">"INICIO"</annotation>" para ver los controles"</string>
<string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de imagen en imagen."</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mover hacia la izquierda"</string>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 2a38766..679bfb9 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -197,6 +197,8 @@
<!-- Freeform window caption strings -->
<!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
<string name="maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the minimize window button [CHAR LIMIT=NONE] -->
+ <string name="minimize_button_text">Minimize</string>
<!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
<string name="close_button_text">Close</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 5cba3b4..6ae0f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -55,6 +55,7 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -72,6 +73,7 @@
*/
public class ShellTaskOrganizer extends TaskOrganizer implements
CompatUIController.CompatUICallback {
+ private static final String TAG = "ShellTaskOrganizer";
// Intentionally using negative numbers here so the positive numbers can be used
// for task id specific listeners that will be added later.
@@ -90,8 +92,6 @@
})
public @interface TaskListenerType {}
- private static final String TAG = "ShellTaskOrganizer";
-
/**
* Callbacks for when the tasks change in the system.
*/
@@ -177,6 +177,9 @@
@Nullable
private final CompatUIController mCompatUI;
+ @NonNull
+ private final ShellCommandHandler mShellCommandHandler;
+
@Nullable
private final Optional<RecentTasksController> mRecentTasks;
@@ -187,29 +190,33 @@
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor) {
- this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
+ this(null /* shellInit */, null /* shellCommandHandler */,
+ null /* taskOrganizerController */, null /* compatUI */,
Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */,
mainExecutor);
}
public ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
- this(shellInit, null /* taskOrganizerController */, compatUI,
+ this(shellInit, shellCommandHandler, null /* taskOrganizerController */, compatUI,
unfoldAnimationController, recentTasks, mainExecutor);
}
@VisibleForTesting
protected ShellTaskOrganizer(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
@Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
super(taskOrganizerController, mainExecutor);
+ mShellCommandHandler = shellCommandHandler;
mCompatUI = compatUI;
mRecentTasks = recentTasks;
mUnfoldAnimationController = unfoldAnimationController.orElse(null);
@@ -219,6 +226,7 @@
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
if (mCompatUI != null) {
mCompatUI.setCompatUICallback(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index d28a68a..a8764e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -54,7 +54,10 @@
/** Callback for listening task state. */
public interface Listener {
- /** Called when the container is ready for launching activities. */
+ /**
+ * Only called once when the surface has been created & the container is ready for
+ * launching activities.
+ */
default void onInitialized() {}
/** Called when the container can no longer launch activities. */
@@ -80,12 +83,13 @@
private final SyncTransactionQueue mSyncQueue;
private final TaskViewTransitions mTaskViewTransitions;
- private ActivityManager.RunningTaskInfo mTaskInfo;
+ protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mSurfaceCreated;
private boolean mIsInitialized;
+ private boolean mNotifiedForInitialized;
private Listener mListener;
private Executor mListenerExecutor;
private Region mObscuredTouchRegion;
@@ -110,6 +114,13 @@
mGuard.open("release");
}
+ /**
+ * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise.
+ */
+ public boolean isInitialized() {
+ return mIsInitialized;
+ }
+
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -269,11 +280,17 @@
resetTaskInfo();
});
mGuard.close();
- if (mListener != null && mIsInitialized) {
+ mIsInitialized = false;
+ notifyReleased();
+ }
+
+ /** Called when the {@link TaskView} has been released. */
+ protected void notifyReleased() {
+ if (mListener != null && mNotifiedForInitialized) {
mListenerExecutor.execute(() -> {
mListener.onReleased();
});
- mIsInitialized = false;
+ mNotifiedForInitialized = false;
}
}
@@ -407,12 +424,8 @@
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurfaceCreated = true;
- if (mListener != null && !mIsInitialized) {
- mIsInitialized = true;
- mListenerExecutor.execute(() -> {
- mListener.onInitialized();
- });
- }
+ mIsInitialized = true;
+ notifyInitialized();
mShellExecutor.execute(() -> {
if (mTaskToken == null) {
// Nothing to update, task is not yet available
@@ -430,6 +443,16 @@
});
}
+ /** Called when the {@link TaskView} is initialized. */
+ protected void notifyInitialized() {
+ if (mListener != null && !mNotifiedForInitialized) {
+ mNotifiedForInitialized = true;
+ mListenerExecutor.execute(() -> {
+ mListener.onInitialized();
+ });
+ }
+ }
+
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mTaskToken == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 312af4f..ee8c414 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -22,7 +22,6 @@
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FlingAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.FrameCallbackScheduler
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
@@ -125,12 +124,6 @@
private var defaultFling: FlingConfig = globalDefaultFling
/**
- * FrameCallbackScheduler to use if it need custom FrameCallbackScheduler, if this is null,
- * it will use the default FrameCallbackScheduler in the DynamicAnimation.
- */
- private var customScheduler: FrameCallbackScheduler? = null
-
- /**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
* just one permanent update and end listener to the DynamicAnimations.
@@ -454,14 +447,6 @@
this.defaultFling = defaultFling
}
- /**
- * Set the custom FrameCallbackScheduler for all aniatmion in this animator. Set this with null for
- * restoring to default FrameCallbackScheduler.
- */
- fun setCustomScheduler(scheduler: FrameCallbackScheduler) {
- this.customScheduler = scheduler
- }
-
/** Starts the animations! */
fun start() {
startAction()
@@ -511,12 +496,9 @@
// springs) on this property before flinging.
cancel(animatedProperty)
- // Apply the custom animation scheduler if it not null
- val flingAnim = getFlingAnimation(animatedProperty, target)
- flingAnim.scheduler = customScheduler ?: flingAnim.scheduler
-
// Apply the configuration and start the animation.
- flingAnim.also { flingConfig.applyToAnimation(it) }.start()
+ getFlingAnimation(animatedProperty, target)
+ .also { flingConfig.applyToAnimation(it) }.start()
}
}
@@ -529,18 +511,6 @@
// Apply the configuration and start the animation.
val springAnim = getSpringAnimation(animatedProperty, target)
- // If customScheduler is exist and has not been set to the animation,
- // it should set here.
- if (customScheduler != null &&
- springAnim.scheduler != customScheduler) {
- // Cancel the animation before set animation handler
- if (springAnim.isRunning) {
- cancel(animatedProperty)
- }
- // Apply the custom scheduler handler if it not null
- springAnim.scheduler = customScheduler ?: springAnim.scheduler
- }
-
// Apply the configuration and start the animation.
springConfig.applyToAnimation(springAnim)
animationStartActions.add(springAnim::start)
@@ -596,12 +566,9 @@
}
}
- // Apply the custom animation scheduler if it not null
- val springAnim = getSpringAnimation(animatedProperty, target)
- springAnim.scheduler = customScheduler ?: springAnim.scheduler
-
// Apply the configuration and start the spring animation.
- springAnim.also { springConfig.applyToAnimation(it) }.start()
+ getSpringAnimation(animatedProperty, target)
+ .also { springConfig.applyToAnimation(it) }.start()
}
}
})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d3dc7e8..ebf8c03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -57,6 +57,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -91,13 +92,14 @@
* Raw delta between {@link #mInitTouchLocation} and the last touch location.
*/
private final Point mTouchEventDelta = new Point();
- private final ShellExecutor mShellExecutor;
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
/** Tracks if an uninterruptible transition is in progress */
private boolean mTransitionInProgress = false;
+ /** Tracks if we should start the back gesture on the next motion move event */
+ private boolean mShouldStartOnNextMoveEvent = false;
/** @see #setTriggerBack(boolean) */
private boolean mTriggerBack;
@@ -106,6 +108,9 @@
private final SurfaceControl.Transaction mTransaction;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final ShellExecutor mShellExecutor;
+ private final Handler mBgHandler;
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
@@ -136,16 +141,19 @@
};
public BackAnimationController(
+ @NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
- this(shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
+ this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
ActivityTaskManager.getService(), context, context.getContentResolver());
}
@VisibleForTesting
- BackAnimationController(@NonNull @ShellMainThread ShellExecutor shellExecutor,
- @NonNull @ShellBackgroundThread Handler handler,
+ BackAnimationController(
+ @NonNull ShellInit shellInit,
+ @NonNull @ShellMainThread ShellExecutor shellExecutor,
+ @NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
@@ -153,7 +161,13 @@
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
- setupAnimationDeveloperSettingsObserver(contentResolver, handler);
+ mContentResolver = contentResolver;
+ mBgHandler = bgHandler;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -289,12 +303,17 @@
if (mTransitionInProgress) {
return;
}
- if (keyAction == MotionEvent.ACTION_MOVE) {
+ if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
+ mShouldStartOnNextMoveEvent = true;
+ }
+ } else if (keyAction == MotionEvent.ACTION_MOVE) {
+ if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
onGestureStarted(touchX, touchY);
+ mShouldStartOnNextMoveEvent = false;
}
onMove(touchX, touchY, swipeEdge);
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
@@ -428,6 +447,11 @@
private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+ if (!mBackGestureStarted) {
+ finishAnimation();
+ return;
+ }
+
if (fromTouch) {
// Let touch reset the flag otherwise it will start a new back navigation and refresh
// the info when received a new move event.
@@ -543,6 +567,7 @@
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
mTriggerBack = false;
+ mShouldStartOnNextMoveEvent = false;
if (backNavigationInfo == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index afc706e..b8204d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
@@ -55,4 +56,7 @@
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index db8d9d4..235fd9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
@@ -119,6 +120,7 @@
private boolean mKeyguardShowing;
public CompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -134,10 +136,14 @@
mSyncQueue = syncQueue;
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
+ mCompatUIHintsState = new CompatUIHintsState();
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addKeyguardChangeListener(this);
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
- mCompatUIHintsState = new CompatUIHintsState();
- shellController.addKeyguardChangeListener(this);
}
/** Sets the callback for UI interactions. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3add417..a6a04cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -128,15 +128,9 @@
mainExecutor);
}
- // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
- @BindsOptionalOf
- @DynamicOverride
- abstract DisplayImeController optionalDisplayImeController();
-
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(
- @DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
IWindowManager wmService,
ShellInit shellInit,
DisplayController displayController,
@@ -144,9 +138,6 @@
TransactionPool transactionPool,
@ShellMainThread ShellExecutor mainExecutor
) {
- if (overrideDisplayImeController.isPresent()) {
- return overrideDisplayImeController.get();
- }
return new DisplayImeController(wmService, shellInit, displayController,
displayInsetsController, transactionPool, mainExecutor);
}
@@ -174,13 +165,14 @@
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
- recentTasksOptional, mainExecutor);
+ return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI,
+ unfoldAnimationController, recentTasksOptional, mainExecutor);
}
@WMSingleton
@@ -188,6 +180,7 @@
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -196,19 +189,20 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler
) {
- return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
- displayController, displayInsetsController, unfoldAnimationController,
- recentTasksOptional, mainExecutor, mainHandler);
+ return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
+ syncTransactionQueue, displayController, displayInsetsController,
+ unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
}
@WMSingleton
@Provides
static CompatUIController provideCompatUIController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
- return new CompatUIController(context, shellController, displayController,
+ return new CompatUIController(context, shellInit, shellController, displayController,
displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
}
@@ -268,12 +262,14 @@
@Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
+ ShellInit shellInit,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellExecutor, backgroundHandler, context));
+ new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
+ context));
}
return Optional.empty();
}
@@ -384,11 +380,14 @@
@WMSingleton
@Provides
static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(
- HideDisplayCutoutController.create(context, shellController, displayController,
- mainExecutor));
+ HideDisplayCutoutController.create(context, shellInit, shellCommandHandler,
+ shellController, displayController, mainExecutor));
}
//
@@ -466,12 +465,13 @@
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, taskStackListener,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellCommandHandler,
+ taskStackListener, mainExecutor));
}
//
@@ -649,8 +649,9 @@
@WMSingleton
@Provides
static ShellController provideShellController(ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(shellInit, mainExecutor);
+ return new ShellController(shellInit, shellCommandHandler, mainExecutor);
}
//
@@ -676,12 +677,15 @@
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<Pip> pipOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<OneHandedController> oneHandedControllerOptional,
+ Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional,
ActivityEmbeddingController activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
@@ -697,18 +701,7 @@
@WMSingleton
@Provides
- static ShellCommandHandler provideShellCommandHandlerImpl(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasksOptional,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandler(shellController, shellTaskOrganizer,
- kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, recentTasksOptional, mainExecutor);
+ static ShellCommandHandler provideShellCommandHandler() {
+ return new ShellCommandHandler();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d53451a..2ca9c3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -74,6 +74,7 @@
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
@@ -248,14 +249,20 @@
@Provides
@DynamicOverride
static OneHandedController provideOneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
- WindowManager windowManager, DisplayController displayController,
- DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor,
- @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, shellController, windowManager,
- displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger,
- mainExecutor, mainHandler);
+ WindowManager windowManager,
+ DisplayController displayController,
+ DisplayLayout displayLayout,
+ TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ InteractionJankMonitor jankMonitor,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
+ windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
+ uiEventLogger, mainExecutor, mainHandler);
}
//
@@ -268,6 +275,7 @@
static SplitScreenController provideSplitScreenController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -281,10 +289,10 @@
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
@ShellMainThread ShellExecutor mainExecutor) {
- return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
- syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
- iconProvider, recentTasks, mainExecutor);
+ return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
+ shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
+ displayImeController, displayInsetsController, dragAndDropController, transitions,
+ transactionPool, iconProvider, recentTasks, mainExecutor);
}
//
@@ -294,24 +302,33 @@
@WMSingleton
@Provides
static Optional<Pip> providePip(Context context,
- ShellController shellController, DisplayController displayController,
- PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
- PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
+ PipAppOpsListener pipAppOpsListener,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
+ PipMediaController pipMediaController,
+ PhonePipMenuController phonePipMenuController,
+ PipTaskOrganizer pipTaskOrganizer,
PipTransitionState pipTransitionState,
- PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(PipController.create(context, shellController, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ oneHandedController, mainExecutor));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 52f0c42..99922fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -59,9 +59,9 @@
adb shell dumpsys activity service SystemUIService WMShell
```
-If information should be added to the dump, make updates to:
-- `WMShell` if you are dumping SysUI state
-- `ShellCommandHandler` if you are dumping Shell state
+If information should be added to the dump, either:
+- Update `WMShell` if you are dumping SysUI state
+- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
## Debugging in Android Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index 0dd50b1..d6302e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -32,7 +32,7 @@
More detail can be found in [go/wm-sysui-dagger](http://go/wm-sysui-dagger).
-## Interfaces to Shell components
+## Interfaces from SysUI to Shell components
Within the same process, the WM Shell components can be running on a different thread than the main
SysUI thread (disabled on certain products). This introduces challenges where we have to be
@@ -54,12 +54,30 @@
Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently
necessary to maintain proper threading and logic isolation.
-## Configuration changes & other SysUI events
+## Listening for Configuration changes & other SysUI events
-Aside from direct calls into Shell controllers for exposed features, the Shell also receives
+Aside from direct calls into Shell controllers for exposed features, the Shell also receives
common event callbacks from SysUI via the `ShellController`. This includes things like:
- Configuration changes
-- TODO: Shell init
-- TODO: Shell command
-- TODO: Keyguard events
\ No newline at end of file
+- Keyguard events
+- Shell init
+- Shell dumps & commands
+
+For other events which are specific to the Shell feature, then you can add callback methods on
+the Shell feature interface. Any such calls should <u>**never**</u> be synchronous calls as
+they will need to post to the Shell main thread to run.
+
+## Shell commands & Dumps
+
+Since the Shell library is a part of the SysUI process, it relies on SysUI to trigger commands
+on individual Shell components, or to dump individual shell components.
+
+```shell
+# Dump everything
+adb shell dumpsys activity service SystemUIService WMShell
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell help
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index af205ed..a1e9f93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -92,6 +92,12 @@
}
@Override
+ public void startMinimizedModeTransition(WindowContainerTransaction wct) {
+ final int type = WindowManager.TRANSIT_TO_BACK;
+ mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this));
+ }
+
+ @Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT,
@@ -121,6 +127,8 @@
transition, info.getType(), change, startT, finishT);
break;
case WindowManager.TRANSIT_TO_BACK:
+ transitionHandled |= startMinimizeTransition(transition);
+ break;
case WindowManager.TRANSIT_TO_FRONT:
break;
}
@@ -169,6 +177,13 @@
return false;
}
+ private boolean startMinimizeTransition(IBinder transition) {
+ if (!mPendingTransitionTokens.contains(transition)) {
+ return false;
+ }
+ return true;
+ }
+
private boolean startChangeTransition(
IBinder transition,
int type,
@@ -243,4 +258,5 @@
return null;
}
}
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
index 25eaa0e..c947cf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -29,6 +29,15 @@
*
* @param targetWindowingMode the target windowing mode
* @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+ *
*/
void startWindowingModeTransition(int targetWindowingMode, WindowContainerTransaction wct);
-}
+
+ /**
+ * Starts window minimization transition
+ *
+ * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+ *
+ */
+ void startMinimizedModeTransition(WindowContainerTransaction wct);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 665b035..32125fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -27,7 +27,9 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -38,6 +40,7 @@
private static final String TAG = "HideDisplayCutoutController";
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final HideDisplayCutoutOrganizer mOrganizer;
@VisibleForTesting
@@ -49,7 +52,10 @@
*/
@Nullable
public static HideDisplayCutoutController create(Context context,
- ShellController shellController, DisplayController displayController,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ DisplayController displayController,
ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
@@ -60,14 +66,24 @@
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, shellController, organizer);
+ return new HideDisplayCutoutController(context, shellInit, shellCommandHandler,
+ shellController, organizer);
}
- HideDisplayCutoutController(Context context, ShellController shellController,
+ HideDisplayCutoutController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
HideDisplayCutoutOrganizer organizer) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOrganizer = organizer;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
updateStatus();
mShellController.addConfigurationChangeListener(this);
}
@@ -96,11 +112,11 @@
updateStatus();
}
- public void dump(@NonNull PrintWriter pw) {
- final String prefix = " ";
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = " ";
pw.print(TAG);
pw.println(" states: ");
- pw.print(prefix);
+ pw.print(innerPrefix);
pw.print("mEnabled=");
pw.println(mEnabled);
mOrganizer.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 73b9b89..2fdd121 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,6 +50,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -73,6 +74,7 @@
private final Handler mMainHandler;
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
@@ -141,6 +143,8 @@
@VisibleForTesting
KidsModeTaskOrganizer(
Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
@@ -150,19 +154,23 @@
KidsModeSettingsObserver kidsModeSettingsObserver,
ShellExecutor mainExecutor,
Handler mainHandler) {
- super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
- unfoldAnimationController, recentTasks, mainExecutor);
+ // Note: we don't call super with the shell init because we will be initializing manually
+ super(/* shellInit= */ null, /* shellCommandHandler= */ null, taskOrganizerController,
+ /* compatUI= */ null, unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
+ shellInit.addInitCallback(this::onInit, this);
}
public KidsModeTaskOrganizer(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
@@ -171,9 +179,10 @@
ShellExecutor mainExecutor,
Handler mainHandler) {
// Note: we don't call super with the shell init because we will be initializing manually
- super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
- mainExecutor);
+ super(/* shellInit= */ null, /* taskOrganizerController= */ null, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
@@ -185,6 +194,9 @@
* Initializes kids mode status.
*/
public void onInit() {
+ if (mShellCommandHandler != null) {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ }
if (mKidsModeSettingsObserver == null) {
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 24f02ac..9149204 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -56,7 +56,9 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -85,6 +87,7 @@
private Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
@@ -192,8 +195,9 @@
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
- public static OneHandedController create(
- Context context, ShellController shellController, WindowManager windowManager,
+ public static OneHandedController create(Context context,
+ ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellController shellController, WindowManager windowManager,
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
@@ -213,14 +217,16 @@
context, displayLayout, settingsUtil, animationController, tutorialHandler,
jankMonitor, mainExecutor);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
- return new OneHandedController(context, shellController, displayController, organizer,
- touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler,
- oneHandedState, oneHandedUiEventsLogger, taskStackListener,
- mainExecutor, mainHandler);
+ return new OneHandedController(context, shellInit, shellCommandHandler, shellController,
+ displayController, organizer, touchHandler, tutorialHandler, settingsUtil,
+ accessibilityUtil, timeoutHandler, oneHandedState, oneHandedUiEventsLogger,
+ taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
@@ -235,6 +241,7 @@
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
@@ -247,8 +254,8 @@
mMainHandler = mainHandler;
mOneHandedUiEventLogger = uiEventsLogger;
mTaskStackListener = taskStackListener;
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
final float offsetPercentageConfig = context.getResources().getFraction(
R.fraction.config_one_handed_offset, 1, 1);
final int sysPropPercentageConfig = SystemProperties.getInt(
@@ -268,6 +275,12 @@
getObserver(this::onSwipeToNotificationEnabledChanged);
mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
mDisplayController.addDisplayChangingController(this);
setupCallback();
registerSettingObservers(mUserId);
@@ -275,7 +288,6 @@
updateSettings();
updateDisplayLayout(mContext.getDisplayId());
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
@@ -623,7 +635,7 @@
updateOneHandedEnabled();
}
- public void dump(@NonNull PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println();
pw.println(TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 38631ce..93172f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -20,7 +20,6 @@
import com.android.wm.shell.common.annotations.ExternalThread;
-import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -99,12 +98,4 @@
* view hierarchy or destroyed.
*/
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
-
- /**
- * Dump the current state and information if need.
- *
- * @param pw The stream to dump information to.
- */
- default void dump(PrintWriter pw) {
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 81e49f8..b32c3ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -29,7 +29,6 @@
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Rect;
-import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TaskSnapshot;
@@ -279,14 +278,15 @@
mEndValue = endValue;
addListener(this);
addUpdateListener(this);
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mTransitionDirection = TRANSITION_DIRECTION_NONE;
}
@Override
public void onAnimationStart(Animator animation) {
mCurrentValue = mStartValue;
- onStartTransaction(mLeash, newSurfaceControlTransaction());
+ onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
}
@@ -294,14 +294,16 @@
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
+ applySurfaceControlTransaction(mLeash,
+ mSurfaceControlTransactionFactory.getTransaction(),
animation.getAnimatedFraction());
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentValue = mEndValue;
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
onEndTransaction(mLeash, tx, mTransitionDirection);
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
@@ -348,7 +350,8 @@
}
void setColorContentOverlay(Context context) {
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
@@ -357,7 +360,8 @@
}
void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
- final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
@@ -406,7 +410,7 @@
void setDestinationBounds(Rect destinationBounds) {
mDestinationBounds.set(destinationBounds);
if (mAnimationType == ANIM_TYPE_ALPHA) {
- onStartTransaction(mLeash, newSurfaceControlTransaction());
+ onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
}
}
@@ -441,16 +445,6 @@
mEndValue = endValue;
}
- /**
- * @return {@link SurfaceControl.Transaction} instance with vsync-id.
- */
- protected SurfaceControl.Transaction newSurfaceControlTransaction() {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- return tx;
- }
-
@VisibleForTesting
public void setSurfaceControlTransactionFactory(
PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3ac08a6..b9746e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -20,6 +20,7 @@
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
@@ -234,4 +235,18 @@
public interface SurfaceControlTransactionFactory {
SurfaceControl.Transaction getTransaction();
}
+
+ /**
+ * Implementation of {@link SurfaceControlTransactionFactory} that returns
+ * {@link SurfaceControl.Transaction} with VsyncId being set.
+ */
+ public static class VsyncSurfaceControlTransactionFactory
+ implements SurfaceControlTransactionFactory {
+ @Override
+ public SurfaceControl.Transaction getTransaction() {
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ return tx;
+ }
+ }
}
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 f747b5e..b46eff6 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
@@ -304,7 +304,8 @@
mSurfaceTransactionHelper = surfaceTransactionHelper;
mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
- mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 586e3a0..fc97f31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -89,7 +89,9 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -122,6 +124,7 @@
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private Optional<OneHandedController> mOneHandedController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
protected final PipImpl mImpl;
@@ -295,6 +298,8 @@
*/
@Nullable
public static Pip create(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -319,16 +324,18 @@
return null;
}
- return new PipController(context, shellController, displayController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController,
+ return new PipController(context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
+ pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
.mImpl;
}
protected PipController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -355,6 +362,7 @@
}
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
@@ -378,11 +386,11 @@
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
- //TODO: move this to ShellInit when PipController can be injected
- mMainExecutor.execute(this::init);
+ shellInit.addInitCallback(this::onInit, this);
}
- public void init() {
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
@@ -503,6 +511,12 @@
updateMovementBounds(null /* toBounds */, false /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
+ } else {
+ // when we enter pip for the first time, the destination bounds and pip
+ // bounds will already match, since they are calculated prior to
+ // starting the animation, so we only need to update the min/max size
+ // that is used for e.g. double tap to maximized state
+ mTouchHandler.updateMinMaxSize(ratio);
}
}
@@ -622,7 +636,8 @@
mPipTaskOrganizer.scheduleAnimateResizePip(
postChangeBounds, duration, null /* updateBoundsCallback */);
} else {
- mTouchHandler.getMotionHelper().movePip(postChangeBounds);
+ // Directly move PiP to its final destination bounds without animation.
+ mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds);
}
} else {
updateDisplayLayout.run();
@@ -912,7 +927,7 @@
return true;
}
- private void dump(PrintWriter pw) {
+ private void dump(PrintWriter pw, String prefix) {
final String innerPrefix = " ";
pw.println(TAG);
mMenuController.dump(pw, innerPrefix);
@@ -1000,18 +1015,6 @@
PipController.this.showPictureInPictureMenu();
});
}
-
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- PipController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to dump PipController in 2s", TAG);
- }
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 5a21e07..44d2202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -33,10 +33,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
-import android.os.Looper;
-import android.view.Choreographer;
-
-import androidx.dynamicanimation.animation.FrameCallbackScheduler;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
@@ -89,25 +85,6 @@
/** Coordinator instance for resolving conflicts with other floating content. */
private FloatingContentCoordinator mFloatingContentCoordinator;
- private ThreadLocal<FrameCallbackScheduler> mSfSchedulerThreadLocal =
- ThreadLocal.withInitial(() -> {
- final Looper initialLooper = Looper.myLooper();
- final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
- @Override
- public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
- // transactions
- Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
- }
-
- @Override
- public boolean isCurrentThread() {
- return Looper.myLooper() == initialLooper;
- }
- };
- return scheduler;
- });
-
/**
* PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
@@ -210,10 +187,8 @@
}
public void init() {
- // Note: Needs to get the shell main thread sf vsync animation handler
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
- mTemporaryBoundsPhysicsAnimator.setCustomScheduler(mSfSchedulerThreadLocal.get());
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index c86c136..a2fa058 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -412,13 +412,7 @@
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
- if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
- } else {
- mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
- mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
- mPipBoundsState.getExpandedBounds().height());
- }
+ updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio);
// The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift
@@ -487,6 +481,27 @@
}
}
+ /**
+ * Update the values for min/max allowed size of picture in picture window based on the aspect
+ * ratio.
+ * @param aspectRatio aspect ratio to use for the calculation of min/max size
+ */
+ public void updateMinMaxSize(float aspectRatio) {
+ updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(),
+ aspectRatio);
+ }
+
+ private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds,
+ float aspectRatio) {
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ } else {
+ mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+ mPipBoundsState.getExpandedBounds().height());
+ }
+ }
+
private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds,
float aspectRatio) {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 3d1a7e9..7b42350 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -62,6 +63,7 @@
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasks mImpl = new RecentTasksImpl();
@@ -87,20 +89,24 @@
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
+ mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellCommandHandler = shellCommandHandler;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mMainExecutor = mainExecutor;
@@ -112,6 +118,7 @@
}
private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 8de649e..2117b69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -84,6 +84,7 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -131,6 +132,7 @@
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
@@ -147,6 +149,7 @@
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
@@ -155,6 +158,7 @@
public SplitScreenController(Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
@@ -168,6 +172,7 @@
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
+ mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -183,6 +188,7 @@
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
@@ -200,6 +206,9 @@
*/
@VisibleForTesting
void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
+ this);
mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -425,7 +434,7 @@
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
// split.
- if (isLaunchingAdjacently(intent.getIntent(), position)) {
+ if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
@@ -440,8 +449,7 @@
/** Returns {@code true} if it's launching the same component on both sides of the split. */
@VisibleForTesting
- boolean isLaunchingAdjacently(@Nullable Intent startIntent,
- @SplitPosition int position) {
+ boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
if (startIntent == null) {
return false;
}
@@ -452,6 +460,16 @@
}
if (isSplitScreenVisible()) {
+ // To prevent users from constantly dropping the same app to the same side resulting in
+ // a large number of instances in the background.
+ final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
+ final ComponentName targetActivity = targetTaskInfo != null
+ ? targetTaskInfo.baseIntent.getComponent() : null;
+ if (Objects.equals(launchingActivity, targetActivity)) {
+ return false;
+ }
+
+ // Allow users to start a new instance the same to adjacent side.
final ActivityManager.RunningTaskInfo pairedTaskInfo =
getTaskInfo(SplitLayout.reversePosition(position));
final ComponentName pairedActivity = pairedTaskInfo != null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
new file mode 100644
index 0000000..681d964
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Handles the shell commands for the SplitscreenController.
+ */
+public class SplitScreenShellCommandHandler implements
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ private final SplitScreenController mController;
+
+ public SplitScreenShellCommandHandler(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ @Override
+ public boolean onShellCommand(String[] args, PrintWriter pw) {
+ switch (args[0]) {
+ case "moveToSideStage":
+ return runMoveToSideStage(args, pw);
+ case "removeFromSideStage":
+ return runRemoveFromSideStage(args, pw);
+ case "setSideStagePosition":
+ return runSetSideStagePosition(args, pw);
+ default:
+ pw.println("Invalid command: " + args[0]);
+ return false;
+ }
+ }
+
+ private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ final int sideStagePosition = args.length > 3
+ ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ mController.moveToSideStage(taskId, sideStagePosition);
+ return true;
+ }
+
+ private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[1]);
+ mController.removeFromSideStage(taskId);
+ return true;
+ }
+
+ private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: side stage position should be provided as arguments");
+ return false;
+ }
+ final int position = new Integer(args[1]);
+ mController.setSideStagePosition(position);
+ return true;
+ }
+
+ @Override
+ public void printShellCommandHelp(PrintWriter pw, String prefix) {
+ pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
+ pw.println(prefix + " Move a task with given id in split-screen mode.");
+ pw.println(prefix + "removeFromSideStage <taskId>");
+ pw.println(prefix + " Remove a task with given id in split-screen mode.");
+ pw.println(prefix + "setSideStagePosition <SideStagePosition>");
+ pw.println(prefix + " Sets the position of the side-stage.");
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 8e1ae39..4bc8e91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -32,13 +32,13 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -147,9 +147,6 @@
private static final String TAG = StageCoordinator.class.getSimpleName();
- /** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
-
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
@@ -894,6 +891,7 @@
}
});
mShouldUpdateRecents = false;
+ mIsDividerRemoteAnimating = false;
if (childrenToTop == null) {
mSideStage.removeAllTasks(wct, false /* toTop */);
@@ -1808,7 +1806,8 @@
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
- shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ shouldAnimate = startPendingEnterAnimation(
+ transition, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingRecent(transition)) {
shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
@@ -1836,7 +1835,8 @@
}
private boolean startPendingEnterAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction finishT) {
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
@@ -1883,8 +1883,8 @@
+ " before startAnimation().");
}
- finishEnterSplitScreen(t);
- addDividerBarToTransition(info, t, true /* show */);
+ finishEnterSplitScreen(finishT);
+ addDividerBarToTransition(info, finishT, true /* show */);
return true;
}
@@ -1969,7 +1969,7 @@
return false;
}
- addDividerBarToTransition(info, t, false /* show */);
+ addDividerBarToTransition(info, finishT, false /* show */);
return true;
}
@@ -1980,23 +1980,25 @@
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, boolean show) {
+ @NonNull SurfaceControl.Transaction finishT, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
- final Rect bounds = mSplitLayout.getDividerBounds();
- barChange.setStartAbsBounds(bounds);
- barChange.setEndAbsBounds(bounds);
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ barChange.setStartAbsBounds(mTempRect1);
+ barChange.setEndAbsBounds(mTempRect1);
barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
barChange.setFlags(FLAG_IS_DIVIDER_BAR);
// Technically this should be order-0, but this is running after layer assignment
// and it's a special case, so just add to end.
info.addChange(barChange);
- // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+
if (show) {
- t.setAlpha(leash, 1.f);
- t.setLayer(leash, Integer.MAX_VALUE);
- t.setPosition(leash, bounds.left, bounds.top);
- t.show(leash);
+ finishT.setLayer(leash, Integer.MAX_VALUE);
+ finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
+ finishT.show(leash);
+ // Ensure divider surface are re-parented back into the hierarchy at the end of the
+ // transition. See Transition#buildFinishTransaction for more detail.
+ finishT.reparent(leash, mRootTaskLeash);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index f4fc0c4..2e6ddc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -16,19 +16,14 @@
package com.android.wm.shell.sysui;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
@@ -38,52 +33,61 @@
public final class ShellCommandHandler {
private static final String TAG = ShellCommandHandler.class.getSimpleName();
- private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<Pip> mPipOptional;
- private final Optional<OneHandedController> mOneHandedOptional;
- private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
- private final Optional<RecentTasksController> mRecentTasks;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
+ // We're using a TreeMap to keep them sorted by command name
+ private final TreeMap<String, BiConsumer<PrintWriter, String>> mDumpables = new TreeMap<>();
+ private final TreeMap<String, ShellCommandActionHandler> mCommands = new TreeMap<>();
- public ShellCommandHandler(
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<RecentTasksController> recentTasks,
- ShellExecutor mainExecutor) {
- mShellTaskOrganizer = shellTaskOrganizer;
- mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
- mRecentTasks = recentTasks;
- mSplitScreenOptional = splitScreenOptional;
- mPipOptional = pipOptional;
- mOneHandedOptional = oneHandedOptional;
- mHideDisplayCutout = hideDisplayCutout;
- // TODO(238217847): To be removed once the command handler dependencies are inverted
- shellController.setShellCommandHandler(this);
+ public interface ShellCommandActionHandler {
+ /**
+ * Handles the given command.
+ *
+ * @param args the arguments starting with the action name, then the action arguments
+ * @param pw the write to print output to
+ */
+ boolean onShellCommand(String[] args, PrintWriter pw);
+
+ /**
+ * Prints the help for this class of commands. Implementations do not need to print the
+ * command class.
+ */
+ void printShellCommandHelp(PrintWriter pw, String prefix);
+ }
+
+
+ /**
+ * Adds a callback to run when the Shell is being dumped.
+ *
+ * @param callback the callback to be made when Shell is dumped, takes the print writer and
+ * a prefix
+ * @param instance used for debugging only
+ */
+ public <T> void addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance) {
+ mDumpables.put(instance.getClass().getSimpleName(), callback);
+ ProtoLog.v(WM_SHELL_INIT, "Adding dump callback for %s",
+ instance.getClass().getSimpleName());
+ }
+
+ /**
+ * Adds an action callback to be invoked when the user runs that particular command from adb.
+ *
+ * @param commandClass the top level class of command to invoke
+ * @param actions the interface to callback when an action of this class is invoked
+ * @param instance used for debugging only
+ */
+ public <T> void addCommandCallback(String commandClass, ShellCommandActionHandler actions,
+ T instance) {
+ mCommands.put(commandClass, actions);
+ ProtoLog.v(WM_SHELL_INIT, "Adding command class %s for %s", commandClass,
+ instance.getClass().getSimpleName());
}
/** Dumps WM Shell internal state. */
public void dump(PrintWriter pw) {
- mShellTaskOrganizer.dump(pw, "");
- pw.println();
- pw.println();
- mPipOptional.ifPresent(pip -> pip.dump(pw));
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
- mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
- pw.println();
- pw.println();
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
- pw.println();
- pw.println();
- mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
- pw.println();
- pw.println();
- mKidsModeTaskOrganizer.dump(pw, "");
+ for (String key : mDumpables.keySet()) {
+ final BiConsumer<PrintWriter, String> r = mDumpables.get(key);
+ r.accept(pw, "");
+ pw.println();
+ }
}
@@ -93,72 +97,32 @@
// Argument at position 0 is "WMShell".
return false;
}
- switch (args[1]) {
- case "moveToSideStage":
- return runMoveToSideStage(args, pw);
- case "removeFromSideStage":
- return runRemoveFromSideStage(args, pw);
- case "setSideStagePosition":
- return runSetSideStagePosition(args, pw);
- case "help":
- return runHelp(pw);
- default:
- return false;
- }
- }
- private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
+ final String cmdClass = args[1];
+ if (cmdClass.toLowerCase().equals("help")) {
+ return runHelp(pw);
+ }
+ if (!mCommands.containsKey(cmdClass)) {
return false;
}
- final int taskId = new Integer(args[2]);
- final int sideStagePosition = args.length > 3
- ? new Integer(args[3]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
- return true;
- }
- private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as arguments");
- return false;
- }
- final int taskId = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId));
- return true;
- }
-
- private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First arguments are "WMShell" and command name.
- pw.println("Error: side stage position should be provided as arguments");
- return false;
- }
- final int position = new Integer(args[2]);
- mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position));
+ // Only pass the actions onwards as arguments to the callback
+ final ShellCommandActionHandler actions = mCommands.get(args[1]);
+ final String[] cmdClassArgs = Arrays.copyOfRange(args, 2, args.length);
+ actions.onShellCommand(cmdClassArgs, pw);
return true;
}
private boolean runHelp(PrintWriter pw) {
pw.println("Window Manager Shell commands:");
+ for (String commandClass : mCommands.keySet()) {
+ pw.println(" " + commandClass);
+ mCommands.get(commandClass).printShellCommandHelp(pw, " ");
+ }
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" <no arguments provided>");
- pw.println(" Dump Window Manager Shell internal state");
- pw.println(" pair <taskId1> <taskId2>");
- pw.println(" unpair <taskId>");
- pw.println(" Pairs/unpairs tasks with given ids.");
- pw.println(" moveToSideStage <taskId> <SideStagePosition>");
- pw.println(" Move a task with given id in split-screen mode.");
- pw.println(" removeFromSideStage <taskId>");
- pw.println(" Remove a task with given id in split-screen mode.");
- pw.println(" setSideStageOutline <true/false>");
- pw.println(" Enable/Disable outline on the side-stage.");
- pw.println(" setSideStagePosition <SideStagePosition>");
- pw.println(" Sets the position of the side-stage.");
+ pw.println(" Dump all Window Manager Shell internal state");
return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index f1f317f..52ffb46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -45,11 +45,10 @@
private static final String TAG = ShellController.class.getSimpleName();
private final ShellInit mShellInit;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private ShellCommandHandler mShellCommandHandler;
-
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
@@ -57,8 +56,10 @@
private Configuration mLastConfiguration;
- public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+ public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler,
+ ShellExecutor mainExecutor) {
mShellInit = shellInit;
+ mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
}
@@ -70,15 +71,6 @@
}
/**
- * Sets the command handler to call back to.
- * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
- * command handler to other classes.
- */
- public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
- mShellCommandHandler = shellCommandHandler;
- }
-
- /**
* Adds a new configuration listener. The configuration change callbacks are not made in any
* particular order.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index c250e03..ac52235 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -52,6 +52,9 @@
* Adds a callback to the ordered list of callbacks be made when Shell is first started. This
* can be used in class constructors when dagger is used to ensure that the initialization order
* matches the dependency order.
+ *
+ * @param r the callback to be made when Shell is initialized
+ * @param instance used for debugging only
*/
public <T extends Object> void addInitCallback(Runnable r, T instance) {
if (mHasInitialized) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 5cce6b9..e26c259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -20,9 +20,9 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
-import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index a843b2a..45b69f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -162,13 +162,12 @@
.setParent(mAnimLeash)
.setBLASTLayer()
.setSecure(screenshotBuffer.containsSecureLayers())
+ .setOpaque(true)
.setCallsite("ShellRotationAnimation")
.setName("RotationLayer")
.build();
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
- t.setPosition(mAnimLeash, 0, 0);
- t.setAlpha(mAnimLeash, 1);
t.show(mAnimLeash);
final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
@@ -181,6 +180,7 @@
mBackColorSurface = new SurfaceControl.Builder(session)
.setParent(rootLeash)
.setColorLayer()
+ .setOpaque(true)
.setCallsite("ShellRotationAnimation")
.setName("BackColorSurface")
.build();
@@ -189,7 +189,6 @@
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
t.show(mBackColorSurface);
}
@@ -242,7 +241,6 @@
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
- t.setAlpha(mScreenshotLayer, (float) 1.0);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 279d57a..9335438 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -119,6 +119,8 @@
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
+ private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -242,6 +244,16 @@
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ /** Registers an observer on the lifecycle of transitions. */
+ public void registerObserver(@NonNull TransitionObserver observer) {
+ mObservers.add(observer);
+ }
+
+ /** Unregisters the observer. */
+ public void unregisterObserver(@NonNull TransitionObserver observer) {
+ mObservers.remove(observer);
+ }
+
/** Boosts the process priority of remote animation player. */
public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
if (appThread == null) return;
@@ -407,6 +419,11 @@
+ Arrays.toString(mActiveTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
+
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
+ }
+
if (!info.getRootLeash().isValid()) {
// Invalid root-leash implies that the transition is empty/no-op, so just do
// housekeeping and return.
@@ -474,6 +491,10 @@
}
private void playTransition(@NonNull ActiveTransition active) {
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionStarting(active.mToken);
+ }
+
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// If a handler already chose to run this animation, try delegating to it first.
@@ -546,6 +567,10 @@
active.mHandler.onTransitionConsumed(
active.mToken, abort, abort ? null : active.mFinishT);
}
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionMerged(
+ active.mToken, mActiveTransitions.get(0).mToken);
+ }
return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -555,6 +580,9 @@
active.mHandler.onTransitionConsumed(
transition, true /* aborted */, null /* finishTransaction */);
}
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
// Merge all relevant transactions together
@@ -593,6 +621,9 @@
transition, true /* aborted */, null /* finishTransaction */);
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionFinished(active.mToken, true);
+ }
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -792,6 +823,52 @@
default void setAnimScaleSetting(float scale) {}
}
+ /**
+ * Interface for something that needs to know the lifecycle of some transitions, but never
+ * handles any transition by itself.
+ */
+ public interface TransitionObserver {
+ /**
+ * Called when the transition is ready to play. It may later be merged into other
+ * transitions. Note this doesn't mean this transition will be played anytime soon.
+ *
+ * @param transition the unique token of this transition
+ * @param startTransaction the transaction given to the handler to be applied before the
+ * transition animation. This will be applied when the transition
+ * handler that handles this transition starts the transition.
+ * @param finishTransaction the transaction given to the handler to be applied after the
+ * transition animation. The Transition system will apply it when
+ * finishCallback is called by the transition handler.
+ */
+ void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction);
+
+ /**
+ * Called when the transition is starting to play. It isn't called for merged transitions.
+ *
+ * @param transition the unique token of this transition
+ */
+ void onTransitionStarting(@NonNull IBinder transition);
+
+ /**
+ * Called when a transition is merged into another transition. There won't be any following
+ * lifecycle calls for the merged transition.
+ *
+ * @param merged the unique token of the transition that's merged to another one
+ * @param playing the unique token of the transition that accepts the merge
+ */
+ void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
+
+ /**
+ * Called when the transition is finished. This isn't called for merged transitions.
+ *
+ * @param transition the unique token of this transition
+ * @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
+ */
+ void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
+ }
+
@BinderThread
private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 08d6c50..e7695926 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -51,7 +51,6 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
-
private FreeformTaskTransitionStarter mTransitionStarter;
public CaptionWindowDecorViewModel(
@@ -168,6 +167,14 @@
} else {
mSyncQueue.queue(wct);
}
+ } else if (id == R.id.minimize_window) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, false);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startMinimizedModeTransition(wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index dc212fc..98b5ee9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -161,12 +161,13 @@
*/
private void setupRootView() {
View caption = mResult.mRootView.findViewById(R.id.caption);
-
caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window);
maximize.setOnClickListener(mOnCaptionButtonClickListener);
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
+ View minimize = caption.findViewById(R.id.minimize_window);
+ minimize.setOnClickListener(mOnCaptionButtonClickListener);
}
void setCaptionColor(int captionColor) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 1e4d23c..9da5179 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -20,6 +20,7 @@
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.region.Region
@@ -88,18 +89,13 @@
splitLeftTop: Boolean
) {
assertLayers {
- val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
this.isInvisible(component)
.then()
- .invoke("splitAppLayerBoundsBecomesVisible") {
- it.visibleRegion(component).coversAtMost(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, endRotation)
- } else {
- getSplitRightBottomRegion(dividerRegion, endRotation)
- }
- )
- }
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .isVisible(component)
+ .then()
+ .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
}
}
@@ -108,16 +104,7 @@
splitLeftTop: Boolean
) {
assertLayers {
- val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- this.invoke("splitAppLayerBoundsBecomesVisible") {
- it.visibleRegion(component).coversAtMost(
- if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, endRotation)
- } else {
- getSplitRightBottomRegion(dividerRegion, endRotation)
- }
- )
- }
+ this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
.then()
.isVisible(component, true)
.then()
@@ -141,6 +128,40 @@
}
}
+fun FlickerTestParameter.splitAppLayerBoundsChanges(
+ component: IComponentMatcher,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ if (splitLeftTop) {
+ this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ } else {
+ this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ }
+ }
+}
+
+fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
+ component: IComponentMatcher,
+ splitLeftTop: Boolean,
+ rotation: Int
+): LayersTraceSubject {
+ return invoke("splitAppLayerBoundsSnapToDivider") {
+ val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ it.visibleRegion(component).coversAtMost(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, rotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, rotation)
+ }
+ )
+ }
+}
+
fun FlickerTestParameter.appWindowBecomesVisible(
component: IComponentMatcher
) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 3708e5f..8b717a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -23,3 +23,4 @@
val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentMatcher("", "AppPairSplitDivider#")
val DOCKED_STACK_DIVIDER_COMPONENT = ComponentMatcher("", "DockedStackDivider#")
val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentMatcher("", "SplitDecorManager#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 4877442..42b7b11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -30,6 +30,7 @@
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
@@ -220,6 +221,23 @@
}
}
+ fun dragDividerToResizeAndWait(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(Point(displayBounds.width * 2 / 3, displayBounds.height * 2 / 3))
+
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+ .waitForAndVerify()
+ }
+
fun dragDividerToDismissSplit(
device: UiDevice,
wmHelper: WindowManagerStateHelper
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
new file mode 100644
index 0000000..cf2dc39
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.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.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test resize split by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+ transitions {
+ SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() {
+ testSpec.assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerKeepVisible() {
+ testSpec.assertLayers {
+ this.isVisible(primaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerVisibilityChanges() {
+ testSpec.assertLayers {
+ this.isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowKeepVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(primaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowKeepVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
+ secondaryApp, splitLeftTop = true)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index c48f3f7..f69eb8f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -30,7 +30,6 @@
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import org.junit.Assume
@@ -104,7 +103,7 @@
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp, splitLeftTop = true)
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 0000000..c23cdb6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.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.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.workspace.switchToOverview()
+ .currentTask
+ .open()
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7517e8a..f865649 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -59,6 +59,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -85,10 +86,12 @@
@Mock
private CompatUIController mCompatUI;
@Mock
- private ShellInit mShellInit;
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
- ShellTaskOrganizer mOrganizer;
- private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
+ private ShellTaskOrganizer mOrganizer;
+ private ShellInit mShellInit;
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -132,8 +135,11 @@
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
- mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+ mShellInit = spy(new ShellInit(mTestExecutor));
+ mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mCompatUI, Optional.empty(), Optional.empty(),
+ mTestExecutor));
+ mShellInit.init();
}
@Test
@@ -142,9 +148,12 @@
}
@Test
- public void testRegisterOrganizer_sendRegisterTaskOrganizer() throws RemoteException {
- mOrganizer.registerOrganizer();
+ public void instantiate_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+ @Test
+ public void testInit_sendRegisterTaskOrganizer() throws RemoteException {
verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 32f1587..ff1d2990 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -169,6 +169,7 @@
mTaskView.onTaskAppeared(mTaskInfo, mLeash);
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+ assertThat(mTaskView.isInitialized()).isTrue();
verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
}
@@ -178,6 +179,7 @@
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
verify(mViewListener).onInitialized();
+ assertThat(mTaskView.isInitialized()).isTrue();
// No task, no visibility change
verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
}
@@ -189,6 +191,7 @@
mTaskView.surfaceCreated(mock(SurfaceHolder.class));
verify(mViewListener).onInitialized();
+ assertThat(mTaskView.isInitialized()).isTrue();
verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
}
@@ -223,6 +226,7 @@
verify(mOrganizer).removeListener(eq(mTaskView));
verify(mViewListener).onReleased();
+ assertThat(mTaskView.isInitialized()).isFalse();
}
@Test
@@ -270,6 +274,7 @@
verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
verify(mViewListener, never()).onInitialized();
+ assertThat(mTaskView.isInitialized()).isFalse();
// If there's no surface the task should be made invisible
verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
}
@@ -281,6 +286,7 @@
verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean());
verify(mViewListener).onInitialized();
+ assertThat(mTaskView.isInitialized()).isTrue();
// No task, no visibility change
verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
}
@@ -353,6 +359,7 @@
verify(mOrganizer).removeListener(eq(mTaskView));
verify(mViewListener).onReleased();
+ assertThat(mTaskView.isInitialized()).isFalse();
verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index ba81602..5b3b8fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -61,6 +62,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
@@ -81,6 +83,7 @@
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+ private ShellInit mShellInit;
@Rule
public TestableContext mContext =
@@ -110,10 +113,12 @@
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
- mController = new BackAnimationController(
+ mShellInit = spy(new ShellInit(mShellExecutor));
+ mController = new BackAnimationController(mShellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
}
@@ -160,6 +165,11 @@
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
@@ -233,10 +243,12 @@
public void animationDisabledFromSettings() throws RemoteException {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
- mController = new BackAnimationController(
+ ShellInit shellInit = new ShellInit(mShellExecutor);
+ mController = new BackAnimationController(shellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ shellInit.init();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
@@ -272,9 +284,14 @@
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
verifyNoMoreInteractions(mIOnBackInvokedCallback);
+ mController.onBackToLauncherAnimationFinished();
+
+ // Verify that more events from a rejected swipe cannot start animation.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verifyNoMoreInteractions(mIOnBackInvokedCallback);
// Verify that we start accepting gestures again once transition finishes.
- mController.onBackToLauncherAnimationFinished();
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
verify(mIOnBackInvokedCallback).onBackStarted();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index 514390f..d467b39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -47,6 +47,7 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
/**
* Tests for {@link DisplayLayout}.
@@ -62,6 +63,7 @@
public void setup() {
mMockitoSession = mockitoSession()
.initMocks(this)
+ .strictness(Strictness.WARN)
.mockStatic(SystemBarUtils.class)
.startMocking();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 828c13e..6292130 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -54,6 +55,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -79,6 +81,7 @@
private static final int TASK_ID = 12;
private CompatUIController mController;
+ private ShellInit mShellInit;
private @Mock ShellController mMockShellController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
@@ -107,9 +110,10 @@
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
- mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
- mMockTransitionsLazy) {
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mController = new CompatUIController(mContext, mShellInit, mMockShellController,
+ mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
+ mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -122,10 +126,16 @@
return mMockLetterboxEduLayout;
}
};
+ mShellInit.init();
spyOn(mController);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index dcc504a..6c301bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.hidedisplaycutout;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,7 +31,10 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -45,17 +50,32 @@
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellController mShellController;
@Mock
private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
private HideDisplayCutoutController mHideDisplayCutoutController;
+ private ShellInit mShellInit;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mHideDisplayCutoutController = new HideDisplayCutoutController(
- mContext, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit = spy(new ShellInit(mock(ShellExecutor.class)));
+ mHideDisplayCutoutController = new HideDisplayCutoutController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mMockDisplayAreaOrganizer);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index a919ad0..ecfb427 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,6 +49,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -74,6 +75,7 @@
@Mock private WindowContainerTransaction mTransaction;
@Mock private KidsModeSettingsObserver mObserver;
@Mock private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
KidsModeTaskOrganizer mOrganizer;
@@ -87,14 +89,20 @@
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
- mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
- Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
+ mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
+ mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
+ mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
+ mTestExecutor, mHandler));
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testKidsModeOn() {
doReturn(true).when(mObserver).isEnabled();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index dbf93ae..90645ce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -36,7 +36,6 @@
import android.graphics.Rect;
import android.os.Handler;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
@@ -49,7 +48,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,18 +62,20 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedControllerTest extends OneHandedTestCase {
- private int mCurrentUser = UserHandle.myUserId();
Display mDisplay;
OneHandedAccessibilityUtil mOneHandedAccessibilityUtil;
OneHandedController mSpiedOneHandedController;
OneHandedTimeoutHandler mSpiedTimeoutHandler;
OneHandedState mSpiedTransitionState;
+ ShellInit mShellInit;
@Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
- DisplayLayout mDisplayLayout;
+ DisplayLayout mMockDisplayLayout;
@Mock
DisplayController mMockDisplayController;
@Mock
@@ -102,7 +105,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mDisplay = mContext.getDisplay();
- mDisplayLayout = Mockito.mock(DisplayLayout.class);
+ mMockDisplayLayout = Mockito.mock(DisplayLayout.class);
mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor));
mSpiedTransitionState = spy(new OneHandedState());
@@ -122,11 +125,14 @@
when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn(
new Rect(0, 0, 1080, 2400));
- when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout);
+ when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mMockDisplayLayout);
+ mShellInit = spy(new ShellInit(mMockShellMainExecutor));
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
@@ -141,6 +147,17 @@
mMockShellMainExecutor,
mMockShellMainHandler)
);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
}
@Test
@@ -308,9 +325,9 @@
@Test
public void testRotation90CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -320,10 +337,10 @@
@Test
public void testRotation180CanStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180);
mSpiedTransitionState.setState(STATE_NONE);
when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true);
- when(mDisplayLayout.isLandscape()).thenReturn(false);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(false);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
@@ -333,9 +350,9 @@
@Test
public void testRotation270CanNotStartOneHanded() {
- mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
+ mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270);
mSpiedTransitionState.setState(STATE_NONE);
- when(mDisplayLayout.isLandscape()).thenReturn(true);
+ when(mMockDisplayLayout.isLandscape()).thenReturn(true);
mSpiedOneHandedController.setOneHandedEnabled(true);
mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */);
mSpiedOneHandedController.startOneHanded();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index e6a8220..a39bdf0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -41,7 +41,9 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,10 @@
OneHandedState mSpiedState;
@Mock
+ ShellInit mMockShellInit;
+ @Mock
+ ShellCommandHandler mMockShellCommandHandler;
+ @Mock
ShellController mMockShellController;
@Mock
DisplayController mMockDisplayController;
@@ -111,6 +117,8 @@
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mMockShellInit,
+ mMockShellCommandHandler,
mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index f192514..9ed8d84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,7 +55,9 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -74,7 +76,9 @@
@TestableLooper.RunWithLooper
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
+ private ShellInit mShellInit;
+ @Mock private ShellCommandHandler mMockShellCommandHandler;
@Mock private ShellController mMockShellController;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@@ -105,19 +109,31 @@
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mMockExecutor).execute(any());
- mPipController = new PipController(mContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ mShellInit = spy(new ShellInit(mMockExecutor));
+ mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor);
+ mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@Test
+ public void instantiatePipController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerDumpCallback() {
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerConfigChangeListener() {
verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
}
@@ -149,9 +165,10 @@
when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
- assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipKeepClearAlgorithm,
+ ShellInit shellInit = new ShellInit(mMockExecutor);
+ assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
+ mMockShellController, mMockDisplayController, mMockPipAppOpsListener,
+ mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -217,7 +234,7 @@
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer).scheduleFinishResizePip(any(Rect.class));
}
@Test
@@ -233,7 +250,7 @@
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper, never()).movePip(any(Rect.class));
+ verify(mMockPipTaskOrganizer, never()).scheduleFinishResizePip(any(Rect.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d406a4e..81bb609 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -23,7 +23,9 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -48,6 +50,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -73,21 +76,35 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
- private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
+ private ShellInit mShellInit;
private ShellExecutor mMainExecutor;
@Before
public void setUp() {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mTaskStackListener, mMainExecutor));
- mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+ mShellCommandHandler, mTaskStackListener, mMainExecutor));
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
mMainExecutor);
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class));
+ }
+
+ @Test
+ public void instantiateController_addDumpCallback() {
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(),
+ isA(RecentTasksController.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 10788f9..5a68361 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -20,12 +20,14 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -53,6 +55,7 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -72,8 +75,9 @@
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
- @Mock ShellController mShellController;
@Mock ShellInit mShellInit;
+ @Mock ShellController mShellController;
+ @Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -93,9 +97,10 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
- mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
- mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
- mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mMainExecutor));
}
@Test
@@ -104,14 +109,31 @@
}
@Test
+ public void instantiateController_registerDumpCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ }
+
+ @Test
+ public void instantiateController_registerCommandCallback() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellCommandHandler, times(1)).addCommandCallback(eq("splitscreen"), any(), any());
+ }
+
+ @Test
public void testControllerRegistersKeyguardChangeListener() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
mSplitScreenController.onInit();
verify(mShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
- public void testIsLaunchingAdjacently_notInSplitScreen() {
+ public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
@@ -120,7 +142,7 @@
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.isLaunchingAdjacently(
+ assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
// Verify launching different activity returns false.
@@ -128,28 +150,40 @@
focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.isLaunchingAdjacently(
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
}
@Test
- public void testIsLaunchingAdjacently_inSplitScreen() {
+ public void testShouldAddMultipleTaskFlag_inSplitScreen() {
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
-
- // Verify launching the same activity returns true.
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo pairingTaskInfo =
+ ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
- assertTrue(mSplitScreenController.isLaunchingAdjacently(
+ Intent diffIntent = createStartIntent("diffActivity");
+ ActivityManager.RunningTaskInfo differentTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+
+ // Verify launching the same activity return false.
+ doReturn(sameTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- pairingTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
- assertFalse(mSplitScreenController.isLaunchingAdjacently(
+ // Verify launching the same activity as adjacent returns true.
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ doReturn(sameTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+
+ // Verify launching different activity from adjacent returns false.
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+ doReturn(differentTaskInfo).when(mSplitScreenController)
+ .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 02311ba..39e58ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -45,6 +45,8 @@
@Mock
private ShellInit mShellInit;
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private ShellExecutor mExecutor;
private ShellController mController;
@@ -56,7 +58,7 @@
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
- mController = new ShellController(mShellInit, mExecutor);
+ mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 388792b..b142039 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -43,11 +43,13 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -59,8 +61,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.view.IDisplayWindowListener;
-import android.view.IWindowManager;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -82,6 +82,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
@@ -89,6 +90,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import java.util.ArrayList;
@@ -688,6 +690,204 @@
verify(runnable4, times(1)).run();
}
+ @Test
+ public void testObserverLifecycle_basicTransitionFlow() {
+ Transitions transitions = createTestTransitions();
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken, info, startT, finishT);
+
+ InOrder observerOrder = inOrder(observer);
+ observerOrder.verify(observer).onTransitionReady(transitToken, info, startT, finishT);
+ observerOrder.verify(observer).onTransitionStarting(transitToken);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken), anyBoolean());
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ verify(observer).onTransitionFinished(transitToken, false);
+ }
+
+ @Test
+ public void testObserverLifecycle_queueing() {
+ Transitions transitions = createTestTransitions();
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken1 = new Binder();
+ transitions.requestStartTransition(transitToken1,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+ verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+ IBinder transitToken2 = new Binder();
+ transitions.requestStartTransition(transitToken2,
+ new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+ TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+ verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
+ verify(observer, times(0)).onTransitionStarting(transitToken2);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ // first transition finished
+ verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+ verify(observer, times(1)).onTransitionStarting(transitToken2);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ verify(observer, times(1)).onTransitionFinished(transitToken2, false);
+ }
+
+
+ @Test
+ public void testObserverLifecycle_merging() {
+ Transitions transitions = createTestTransitions();
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+ mDefaultHandler.setSimulateMerge(true);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken1 = new Binder();
+ transitions.requestStartTransition(transitToken1,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
+
+ IBinder transitToken2 = new Binder();
+ transitions.requestStartTransition(transitToken2,
+ new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
+ TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
+
+ InOrder observerOrder = inOrder(observer);
+ observerOrder.verify(observer).onTransitionReady(transitToken2, info2, startT2, finishT2);
+ observerOrder.verify(observer).onTransitionMerged(transitToken2, transitToken1);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ // transition + merged all finished.
+ verify(observer, times(1)).onTransitionFinished(transitToken1, false);
+ // Merged transition won't receive any lifecycle calls beyond ready
+ verify(observer, times(0)).onTransitionStarting(transitToken2);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
+ }
+
+ @Test
+ public void testObserverLifecycle_mergingAfterQueueing() {
+ Transitions transitions = createTestTransitions();
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+ mDefaultHandler.setSimulateMerge(true);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ // Make a test handler that only responds to multi-window triggers AND only animates
+ // Change transitions.
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ TestTransitionHandler testHandler = new TestTransitionHandler() {
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change chg : info.getChanges()) {
+ if (chg.getMode() == TRANSIT_CHANGE) {
+ return super.startAnimation(transition, info, startTransaction,
+ finishTransaction, finishCallback);
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ final RunningTaskInfo task = request.getTriggerTask();
+ return (task != null && task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
+ ? handlerWCT : null;
+ }
+ };
+ transitions.addHandler(testHandler);
+
+ // Use test handler to play an animation
+ IBinder transitToken1 = new Binder();
+ RunningTaskInfo mwTaskInfo =
+ createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ transitions.requestStartTransition(transitToken1,
+ new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
+ TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(TRANSIT_CHANGE).build();
+ SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
+
+ // Request the second transition that should be handled by the default handler
+ IBinder transitToken2 = new Binder();
+ TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.requestStartTransition(transitToken2,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
+ verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
+ verify(observer, times(0)).onTransitionStarting(transitToken2);
+
+ // Request the third transition that should be merged into the second one
+ IBinder transitToken3 = new Binder();
+ transitions.requestStartTransition(transitToken3,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
+ transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
+ verify(observer, times(0)).onTransitionStarting(transitToken2);
+ verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
+ verify(observer, times(0)).onTransitionStarting(transitToken3);
+
+ testHandler.finishAll();
+ mMainExecutor.flushAll();
+
+ verify(observer).onTransitionFinished(transitToken1, false);
+
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+
+ InOrder observerOrder = inOrder(observer);
+ observerOrder.verify(observer).onTransitionStarting(transitToken2);
+ observerOrder.verify(observer).onTransitionMerged(transitToken3, transitToken2);
+ observerOrder.verify(observer).onTransitionFinished(transitToken2, false);
+
+ // Merged transition won't receive any lifecycle calls beyond ready
+ verify(observer, times(0)).onTransitionStarting(transitToken3);
+ verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
+ }
+
class TransitionInfoBuilder {
final TransitionInfo mInfo;
@@ -834,16 +1034,13 @@
}
private DisplayController createTestDisplayController() {
- IWindowManager mockWM = mock(IWindowManager.class);
- final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
- try {
- doReturn(new int[]{DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
- } catch (RemoteException e) {
- // No remote stuff happening, so this can't be hit
- }
- ShellInit shellInit = new ShellInit(mMainExecutor);
- DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
- shellInit.init();
+ DisplayLayout displayLayout = mock(DisplayLayout.class);
+ doReturn(Surface.ROTATION_180).when(displayLayout).getUpsideDownRotation();
+ // By default we ignore nav bar in deciding if a seamless rotation is allowed.
+ doReturn(true).when(displayLayout).allowSeamlessRotationDespiteNavBarMoving();
+
+ DisplayController out = mock(DisplayController.class);
+ doReturn(displayLayout).when(out).getDisplayLayout(DEFAULT_DISPLAY);
return out;
}
@@ -854,17 +1051,4 @@
shellInit.init();
return t;
}
-//
-// private class TestDisplayController extends DisplayController {
-// private final DisplayLayout mTestDisplayLayout;
-// TestDisplayController() {
-// super(mContext, mock(IWindowManager.class), mMainExecutor);
-// mTestDisplayLayout = new DisplayLayout();
-// mTestDisplayLayout.
-// }
-//
-// @Override
-// DisplayLayout
-// }
-
}
diff --git a/libs/hwui/CopyRequest.h b/libs/hwui/CopyRequest.h
new file mode 100644
index 0000000..5fbd5f9
--- /dev/null
+++ b/libs/hwui/CopyRequest.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Rect.h"
+#include "hwui/Bitmap.h"
+
+namespace android::uirenderer {
+
+// Keep in sync with PixelCopy.java codes
+enum class CopyResult {
+ Success = 0,
+ UnknownError = 1,
+ Timeout = 2,
+ SourceEmpty = 3,
+ SourceInvalid = 4,
+ DestinationInvalid = 5,
+};
+
+struct CopyRequest {
+ Rect srcRect;
+ CopyRequest(Rect srcRect) : srcRect(srcRect) {}
+ virtual ~CopyRequest() {}
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) = 0;
+ virtual void onCopyFinished(CopyResult result) = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 1191b92..02c2e67 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -49,8 +49,7 @@
#define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom)
-CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRect,
- SkBitmap* bitmap) {
+void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request) {
ATRACE_CALL();
// Setup the source
AHardwareBuffer* rawSourceBuffer;
@@ -63,30 +62,33 @@
// Really this shouldn't ever happen, but better safe than sorry.
if (err == UNKNOWN_TRANSACTION) {
ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?");
- return copySurfaceIntoLegacy(window, inSrcRect, bitmap);
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect),
windowTransform);
if (err != NO_ERROR) {
ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
if (rawSourceBuffer == nullptr) {
ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
+ return request->onCopyFinished(CopyResult::SourceEmpty);
}
UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
AHardwareBuffer_Desc description;
AHardwareBuffer_describe(sourceBuffer.get(), &description);
if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
+ return request->onCopyFinished(CopyResult::SourceInvalid);
}
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
+ {
+ ATRACE_NAME("sync_wait");
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
+ ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ return request->onCopyFinished(CopyResult::Timeout);
+ }
}
sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
@@ -95,12 +97,12 @@
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
if (!image.get()) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
sk_sp<GrDirectContext> grContext = mRenderThread.requireGrContext();
- SkRect srcRect = inSrcRect.toSkRect();
+ SkRect srcRect = request->srcRect.toSkRect();
SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height);
SkISize imageWH = SkISize::Make(description.width, description.height);
@@ -148,10 +150,12 @@
ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect),
SK_RECT_ARGS(textureRect));
if (!srcRect.intersect(textureRect)) {
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
+ SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
+ SkBitmap* bitmap = &skBitmap;
sk_sp<SkSurface> tmpSurface =
SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
@@ -164,7 +168,7 @@
tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
@@ -235,52 +239,13 @@
!tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) ||
!tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
ALOGW("Unable to convert content into the provided bitmap");
- return CopyResult::UnknownError;
+ return request->onCopyFinished(CopyResult::UnknownError);
}
}
bitmap->notifyPixelsChanged();
- return CopyResult::Success;
-}
-
-CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect,
- SkBitmap* bitmap) {
- // Setup the source
- AHardwareBuffer* rawSourceBuffer;
- int rawSourceFence;
- Matrix4 texTransform;
- status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence,
- texTransform.data);
- base::unique_fd sourceFence(rawSourceFence);
- texTransform.invalidateType();
- if (err != NO_ERROR) {
- ALOGW("Failed to get last queued buffer, error = %d", err);
- return CopyResult::UnknownError;
- }
- if (rawSourceBuffer == nullptr) {
- ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
- return CopyResult::SourceEmpty;
- }
-
- UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer};
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(sourceBuffer.get(), &description);
- if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) {
- ALOGW("Surface is protected, unable to copy from it");
- return CopyResult::SourceInvalid;
- }
-
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
- return CopyResult::Timeout;
- }
-
- sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
- static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
- sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
- return copyImageInto(image, srcRect, bitmap);
+ return request->onCopyFinished(CopyResult::Success);
}
CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
@@ -318,14 +283,14 @@
CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect,
SkBitmap* bitmap) {
ATRACE_CALL();
+ if (!image.get()) {
+ return CopyResult::UnknownError;
+ }
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
mRenderThread.requireGlContext();
} else {
mRenderThread.requireVkContext();
}
- if (!image.get()) {
- return CopyResult::UnknownError;
- }
int imgWidth = image->width();
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index aa6e43c..a092d47 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -16,12 +16,13 @@
#pragma once
+#include <SkRefCnt.h>
+
+#include "CopyRequest.h"
#include "Matrix.h"
#include "Rect.h"
#include "renderthread/RenderThread.h"
-#include <SkRefCnt.h>
-
class SkBitmap;
class SkImage;
struct SkRect;
@@ -35,23 +36,13 @@
class DeferredLayerUpdater;
class Layer;
-// Keep in sync with PixelCopy.java codes
-enum class CopyResult {
- Success = 0,
- UnknownError = 1,
- Timeout = 2,
- SourceEmpty = 3,
- SourceInvalid = 4,
- DestinationInvalid = 5,
-};
-
class Readback {
public:
explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {}
/**
* Copies the surface's most recently queued buffer into the provided bitmap.
*/
- CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
+ void copySurfaceInto(ANativeWindow* window, const std::shared_ptr<CopyRequest>& request);
CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
CopyResult copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap);
@@ -59,7 +50,6 @@
CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
private:
- CopyResult copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
CopyResult copyImageInto(const sk_sp<SkImage>& image, const Rect& srcRect, SkBitmap* bitmap);
bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 87eda7e..1c5f126 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -8,6 +8,8 @@
#include "graphics_jni_helpers.h"
+#include <csetjmp>
+
YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
// Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
// for now.
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 55b1f23..4f281fc 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -88,6 +88,11 @@
jmethodID onFrameComplete;
} gFrameCompleteCallback;
+struct {
+ jmethodID onCopyFinished;
+ jmethodID getDestinationBitmap;
+} gCopyRequest;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -672,15 +677,41 @@
}
}
-static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
- jobject clazz, jobject jsurface, jint left, jint top,
- jint right, jint bottom, jlong bitmapPtr) {
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+class CopyRequestAdapter : public CopyRequest {
+public:
+ CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect)
+ : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ JNIEnv* env = getenv(mRefHolder.vm());
+ jlong bitmapPtr = env->CallLongMethod(
+ mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight);
+ SkBitmap bitmap;
+ bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ JNIEnv* env = getenv(mRefHolder.vm());
+ env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished,
+ static_cast<jint>(result));
+ }
+
+private:
+ JGlobalRefHolder mRefHolder;
+};
+
+static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz,
+ jobject jsurface, jint left, jint top,
+ jint right, jint bottom,
+ jobject jCopyRequest) {
+ JavaVM* vm = nullptr;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
+ auto copyRequest = std::make_shared<CopyRequestAdapter>(vm, env->NewGlobalRef(jCopyRequest),
+ Rect(left, top, right, bottom));
ANativeWindow* window = fromSurface(env, jsurface);
- jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
+ RenderProxy::copySurfaceInto(window, std::move(copyRequest));
ANativeWindow_release(window);
- return result;
}
class ContextFactory : public IContextFactory {
@@ -969,7 +1000,8 @@
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback},
{"nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver},
{"nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver},
- {"nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
+ {"nCopySurfaceInto",
+ "(Landroid/view/Surface;IIIILandroid/graphics/HardwareRenderer$CopyRequest;)V",
(void*)android_view_ThreadedRenderer_copySurfaceInto},
{"nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
(void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode},
@@ -1042,6 +1074,11 @@
gFrameCompleteCallback.onFrameComplete =
GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V");
+ jclass copyRequest = FindClassOrDie(env, "android/graphics/HardwareRenderer$CopyRequest");
+ gCopyRequest.onCopyFinished = GetMethodIDOrDie(env, copyRequest, "onCopyFinished", "(I)V");
+ gCopyRequest.getDestinationBitmap =
+ GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
+
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b2ba15c..40a0bac 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -364,12 +364,13 @@
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
-int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
- SkBitmap* bitmap) {
+void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
auto& thread = RenderThread::getInstance();
- return static_cast<int>(thread.queue().runSync([&]() -> auto {
- return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
- }));
+ ANativeWindow_acquire(window);
+ thread.queue().post([&thread, window, request = std::move(request)] {
+ thread.readback().copySurfaceInto(window, request);
+ ANativeWindow_release(window);
+ });
}
void RenderProxy::prepareToDraw(Bitmap& bitmap) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index bbfeeac..2a99a73 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -19,13 +19,14 @@
#include <SkRefCnt.h>
#include <android/native_window.h>
-#include <cutils/compiler.h>
#include <android/surface_control.h>
+#include <cutils/compiler.h>
#include <utils/Functor.h>
#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
#include "ColorMode.h"
+#include "CopyRequest.h"
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
@@ -137,8 +138,7 @@
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
void setForceDark(bool enable);
- static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
- int bottom, SkBitmap* bitmap);
+ static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
static void prepareToDraw(Bitmap& bitmap);
static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index 070a339..13a4381 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -25,17 +25,51 @@
class MagnifierAnimation;
+using Rect = android::uirenderer::Rect;
+
static TestScene::Registrar _Magnifier(TestScene::Info{
"magnifier", "A sample magnifier using Readback",
TestScene::simpleCreateScene<MagnifierAnimation>});
+class BlockingCopyRequest : public CopyRequest {
+ sk_sp<Bitmap> mDestination;
+ std::mutex mLock;
+ std::condition_variable mCondVar;
+ CopyResult mResult;
+
+public:
+ BlockingCopyRequest(::Rect rect, sk_sp<Bitmap> bitmap)
+ : CopyRequest(rect), mDestination(bitmap) {}
+
+ virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override {
+ SkBitmap bitmap;
+ mDestination->getSkBitmap(&bitmap);
+ return bitmap;
+ }
+
+ virtual void onCopyFinished(CopyResult result) override {
+ std::unique_lock _lock{mLock};
+ mResult = result;
+ mCondVar.notify_all();
+ }
+
+ CopyResult waitForResult() {
+ std::unique_lock _lock{mLock};
+ mCondVar.wait(_lock);
+ return mResult;
+ }
+};
+
class MagnifierAnimation : public TestScene {
public:
sp<RenderNode> card;
sp<RenderNode> zoomImageView;
+ sk_sp<Bitmap> magnifier;
+ std::shared_ptr<BlockingCopyRequest> copyRequest;
void createContent(int width, int height, Canvas& canvas) override {
magnifier = TestUtils::createBitmap(200, 100);
+ setupCopyRequest();
SkBitmap temp;
magnifier->getSkBitmap(&temp);
temp.eraseColor(Color::White);
@@ -65,19 +99,20 @@
canvas.enableZ(false);
}
+ void setupCopyRequest() {
+ constexpr int x = 90;
+ constexpr int y = 325;
+ copyRequest = std::make_shared<BlockingCopyRequest>(
+ ::Rect(x, y, x + magnifier->width(), y + magnifier->height()), magnifier);
+ }
+
void doFrame(int frameNr) override {
int curFrame = frameNr % 150;
card->mutateStagingProperties().setTranslationX(curFrame);
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
if (renderTarget) {
- SkBitmap temp;
- magnifier->getSkBitmap(&temp);
- constexpr int x = 90;
- constexpr int y = 325;
- RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
- y + magnifier->height(), &temp);
+ RenderProxy::copySurfaceInto(renderTarget.get(), copyRequest);
+ copyRequest->waitForResult();
}
}
-
- sk_sp<Bitmap> magnifier;
};
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 55ee3aa..f5a9850 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -47,6 +47,15 @@
* <p>All locations generated through {@link LocationManager} are guaranteed to have a valid
* latitude, longitude, timestamp (both Unix epoch time and elapsed realtime since boot), and
* accuracy. All other parameters are optional.
+ *
+ * <p class="note">Note that Android provides the ability for applications to submit "mock" or faked
+ * locations through {@link LocationManager}, and that these locations can then be received by
+ * applications using LocationManager to obtain location information. These locations can be
+ * identified via the {@link #isMock()} API. Applications that wish to determine if a given location
+ * represents the best estimate of the real position of the device as opposed to a fake location
+ * coming from another application or the user should use this API. Keep in mind that the user may
+ * have a good reason for mocking their location, and thus apps should generally reject mock
+ * locations only when it is essential to their use case that only real locations are accepted.
*/
public class Location implements Parcelable {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 75fd64a..bf30c50 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1445,7 +1445,7 @@
sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
maxChannels = 255;
} else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
- sampleRateRange = Range.create(1, 96000);
+ sampleRateRange = Range.create(1, 192000);
bitRates = Range.create(1, 10000000);
maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
} else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index a83c090..1df9059 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -58,9 +58,6 @@
"zxing-core-1.7",
],
- // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
- // LOCAL_SHARED_JAVA_LIBRARIES := androidx.lifecycle_lifecycle-common
-
resource_dirs: ["res"],
srcs: [
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 56d2967..5364783 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -132,6 +132,10 @@
}
}
+ @Override public CharSequence getTitle() {
+ return mTitle;
+ }
+
@Override
public void setIcon(Drawable icon) {
mIcon = icon;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index f4af9c7..01698b7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,6 +19,7 @@
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
+import android.content.res.Configuration;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.view.LayoutInflater;
@@ -97,7 +98,7 @@
.build()));
}
}
- disableCollapsingToolbarLayoutScrollingBehavior();
+ autoSetCollapsingToolbarLayoutScrolling();
mToolbar = view.findViewById(R.id.action_bar);
mContentFrameLayout = view.findViewById(R.id.content_frame);
final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
@@ -148,7 +149,7 @@
return mAppBarLayout;
}
- private void disableCollapsingToolbarLayoutScrollingBehavior() {
+ private void autoSetCollapsingToolbarLayoutScrolling() {
if (mAppBarLayout == null) {
return;
}
@@ -159,7 +160,9 @@
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- return false;
+ // Header can be scrolling while device in landscape mode.
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 522de93..d67ac3b 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -21,6 +21,7 @@
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
@@ -122,7 +123,7 @@
mCollapsingToolbarLayout.setTitle(mToolbarTitle);
}
}
- disableCollapsingToolbarLayoutScrollingBehavior();
+ autoSetCollapsingToolbarLayoutScrolling();
}
/**
@@ -243,7 +244,7 @@
mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
}
- private void disableCollapsingToolbarLayoutScrollingBehavior() {
+ private void autoSetCollapsingToolbarLayoutScrolling() {
if (mAppBarLayout == null) {
return;
}
@@ -254,7 +255,9 @@
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- return false;
+ // Header can be scrolling while device in landscape mode.
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
index 484b604..5c6b609 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
@@ -22,10 +22,10 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavType
import androidx.navigation.navArgument
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
index b07b180..171a161 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
@@ -25,10 +25,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
import com.android.settingslib.spa.codelab.R
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
object HomePageProvider : SettingsPageProvider {
override val name = Destinations.Home
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
index 86b6843..c24541a 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
@@ -16,7 +16,7 @@
package com.android.settingslib.spa.codelab.page
-import com.android.settingslib.spa.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
object Destinations {
const val Home = "Home"
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
index 81627f6..d53562d 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
@@ -30,10 +30,10 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.framework.navigator
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import kotlinx.coroutines.delay
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
index 28a5899e..5b39b6e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
@@ -24,9 +24,10 @@
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import com.android.settingslib.spa.api.SettingsPageProvider
-import com.android.settingslib.spa.api.SettingsPageRepository
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.api.SettingsPageRepository
+import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.theme.SettingsTheme
open class SpaActivity(
private val settingsPageRepository: SettingsPageRepository,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
index 0ad0003..84daf22 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
import android.os.Bundle
import androidx.compose.runtime.Composable
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
similarity index 93%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
index ce39f4f..4a270b1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/api/SettingsPageRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/api/SettingsPageRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.api
+package com.android.settingslib.spa.framework.api
data class SettingsPageRepository(
val allPages: List<SettingsPageProvider>,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
similarity index 96%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 35112ad..c68d5de 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
similarity index 94%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
index 22d85e0..7c8608d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/RuntimeUtils.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.framework.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
index 8cd9184..4626f0b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/MaterialColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 51e75c5..9a67c12 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
similarity index 92%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
index 5f4da8a..04ee3c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsOpacity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsOpacity.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
object SettingsOpacity {
const val Full = 1f
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
similarity index 95%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index fce9f2b..2999825 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.theme
+package com.android.settingslib.spa.framework.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index d415e9b..6bdc294 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -35,10 +35,10 @@
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsOpacity
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity
+import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
internal fun BaseLayout(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 563a47a..3b99d36 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -26,9 +26,9 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
-import com.android.settingslib.spa.framework.toState
-import com.android.settingslib.spa.theme.SettingsDimension
-import com.android.settingslib.spa.theme.SettingsTheme
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
internal fun BasePreference(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index ee20280..1a80ed2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -20,7 +20,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.stateOf
+import com.android.settingslib.spa.framework.compose.stateOf
/**
* The widget model for [Preference] widget.
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
index 4097946..a92f871 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt
@@ -26,7 +26,7 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.toState
+import com.android.settingslib.spa.framework.compose.toState
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SettingsLib/res/values-af/arrays.xml b/packages/SettingsLib/res/values-af/arrays.xml
index 1de7668..a7e44d3 100644
--- a/packages/SettingsLib/res/values-af/arrays.xml
+++ b/packages/SettingsLib/res/values-af/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Gebruik stelselkeuse (verstek)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Gebruik stelselkeuse (verstek)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-oudio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-oudio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Gebruik stelselkeuse (verstek)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index fe8030f..0b23dd1 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luggehalte"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Uitsaai-inligting"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Huiskontroles"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Kies \'n profielprent"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Verstekgebruikerikoon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fisieke sleutelbord"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3c034f..2bf9cb2 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"የአየር ሁኔታ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"የአየር ጥራት"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"የCast መረጃ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"የቤት ውስጥ ቁጥጥሮች"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"የመገለጫ ሥዕል ይምረጡ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ነባሪ የተጠቃሚ አዶ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 6783654..6c71d9c 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"الطقس"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"جودة الهواء"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"معلومات البث"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"إدارة آلية للمنزل"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"اختيار صورة الملف الشخصي"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"رمز المستخدم التلقائي"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 543c703..299df77 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -121,7 +121,7 @@
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"সম্পৰ্কসূচী আৰু কলৰ ইতিহাস শ্বেয়াৰ কৰাৰ বাবে ব্যৱহাৰ কৰক"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ইণ্টাৰনেট সংযোগ শ্বেয়াৰ"</string>
<string name="bluetooth_profile_map" msgid="8907204701162107271">"পাঠ বাৰ্তা"</string>
- <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিম প্ৰৱেশ"</string>
+ <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string>
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"বতৰ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"বায়ুৰ গুণগত মান"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাষ্টৰ তথ্য"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"গৃহ নিয়ন্ত্ৰণ"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string>
diff --git a/packages/SettingsLib/res/values-az/arrays.xml b/packages/SettingsLib/res/values-az/arrays.xml
index 48974a7..d1f157a 100644
--- a/packages/SettingsLib/res/values-az/arrays.xml
+++ b/packages/SettingsLib/res/values-az/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Sistem Seçimini istifadə edin (Defolt)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Sistem Seçimini istifadə edin (Defolt)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Sistem Seçimini istifadə edin (Defolt)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index f3e5d95..fd79192 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Hava"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havanın keyfiyyəti"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayım məlumatı"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Əsas səhifə kontrolları"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Profil şəkli seçin"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Defolt istifadəçi ikonası"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziki klaviatura"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index 337da26..63b08fa 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Koristi izbor sistema (podrazumevano)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Koristi izbor sistema (podrazumevano)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Koristi izbor sistema (podrazumevano)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 6a880aa..f7d9be9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Vreme"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet vazduha"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o prebacivanju"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravljanje domom"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Podrazumevana ikona korisnika"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index a902426..431bcd5 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Надвор\'е"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якасць паветра"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Даныя пра трансляцыю"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Кіраванне домам"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Выберыце відарыс профілю"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Стандартны карыстальніцкі значок"</string>
diff --git a/packages/SettingsLib/res/values-bg/arrays.xml b/packages/SettingsLib/res/values-bg/arrays.xml
index 1aad6ca7..849e694 100644
--- a/packages/SettingsLib/res/values-bg/arrays.xml
+++ b/packages/SettingsLib/res/values-bg/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Използване на сист. избор (стандартно)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"Разширено аудиокодиране (AAC)"</item>
+ <item msgid="1049450003868150455">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Използване на сист. избор (стандартно)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"Разширено аудиокодиране (AAC)"</item>
+ <item msgid="8627333814413492563">"Аудио: <xliff:g id="APTX">aptX™</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Аудио: <xliff:g id="APTX_HD">aptX™ HD</xliff:g> от <xliff:g id="QUALCOMM">Qualcomm®</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Използване на сист. избор (стандартно)"</item>
<item msgid="8003118270854840095">"44,1 кХц"</item>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index c512366..cbc4322 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Времето"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество на въздуха"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Предаване: Инф."</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за дома"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Изберете снимка на потребителския профил"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Икона за основния потребител"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Физическа клавиатура"</string>
diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml
index 5e6bb95..a3bc4fd 100644
--- a/packages/SettingsLib/res/values-bn/arrays.xml
+++ b/packages/SettingsLib/res/values-bn/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> অডিও"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> অডিও"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"সিস্টেমের নির্বাচন ব্যবহার করুন (ডিফল্ট)"</item>
<item msgid="8003118270854840095">"৪৪.১ kHz"</item>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 23eed04..3394eea 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"এয়ার কোয়ালিটি"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"কাস্ট সম্পর্কিত তথ্য"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"হোম কন্ট্রোল"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"একটি প্রোফাইল ছবি বেছে নিন"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ডিফল্ট ব্যবহারকারীর আইকন"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ফিজিক্যাল কীবোর্ড"</string>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index 262a35f..926ad84 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Korištenje odabira sistema (zadano)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Korištenje odabira sistema (zadano)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Korištenje odabira sistema (zadano)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 78a484f..1547e83 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -265,7 +265,7 @@
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, otklanjanje grešaka, programer"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Prečica za izvještaj o greškama"</string>
<string name="bugreport_in_power_summary" msgid="1885529649381831775">"Vidite dugme za prijavu grešaka u meniju napajanja"</string>
- <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj ekran"</string>
+ <string name="keep_screen_on" msgid="1187161672348797558">"Ne zaključavaj"</string>
<string name="keep_screen_on_summary" msgid="1510731514101925829">"Ekran neće prelaziti u stanje mirovanja tokom punjenja"</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"Omogući Bluetooth HCI snoop zapis"</string>
<string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Snimite Bluetooth pakete. (Uključite/isključite Bluetooth nakon što promijenite ovu postavku)"</string>
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalitet zraka"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Podaci o emitiranju"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrole doma"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Odaberite sliku profila"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Zadana ikona korisnika"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fizička tastatura"</string>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index 8c34a1f..b6f1590 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Utilitza la selecció del sistema (predeterminada)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Utilitza la selecció del sistema (predeterminada)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Àudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Utilitza la selecció del sistema (predeterminada)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 606a379..c97a6c1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Temps"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualitat de l\'aire"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informació d\'emissió"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domòtica"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"D\'una ullada"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Tria una foto de perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icona d\'usuari predeterminat"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclat físic"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 37c0bb4..dfd6d61 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Počasí"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info o odesílání"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládání domácnosti"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Vyberte profilový obrázek"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Výchozí uživatelská ikona"</string>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 155104ae..48a33f6 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Brug systemvalg (standard)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Brug systemvalg (standard)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Brug systemvalg (standard)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d80e1ec..790819c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Vejr"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast-oplysninger"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Styring af hjem"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Overblik"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Vælg et profilbillede"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikon for standardbruger"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-de/arrays.xml b/packages/SettingsLib/res/values-de/arrays.xml
index 31126a8..ca999db 100644
--- a/packages/SettingsLib/res/values-de/arrays.xml
+++ b/packages/SettingsLib/res/values-de/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Systemauswahl verwenden (Standard)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Systemauswahl verwenden (Standard)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-Audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-Audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Systemauswahl verwenden (Standard)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index d4fafe1..bd919f4 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Wetter"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftqualität"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Streaming-Info"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Smart-Home-Steuerung"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Profilbild auswählen"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Standardmäßiges Nutzersymbol"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physische Tastatur"</string>
diff --git a/packages/SettingsLib/res/values-el/arrays.xml b/packages/SettingsLib/res/values-el/arrays.xml
index 70000e1..b95f6fc 100644
--- a/packages/SettingsLib/res/values-el/arrays.xml
+++ b/packages/SettingsLib/res/values-el/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Ήχος <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Χρήση επιλογής συστήματος (Προεπιλογή)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index aa613a5..9326dac 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Καιρός"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ποιότητα αέρα"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Πληροφορίες ηθοποιών"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Οικιακοί έλεγχοι"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Επιλογή φωτογραφίας προφίλ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Προεπιλεγμένο εικονίδιο χρήστη"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Φυσικό πληκτρολόγιο"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/arrays.xml b/packages/SettingsLib/res/values-en-rAU/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rAU/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rAU/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Use system selection (default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Use system selection (default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 8650b77..bf20fcc 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/arrays.xml b/packages/SettingsLib/res/values-en-rGB/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rGB/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rGB/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Use system selection (default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/arrays.xml b/packages/SettingsLib/res/values-en-rIN/arrays.xml
index fc6f791..327e4e9 100644
--- a/packages/SettingsLib/res/values-en-rIN/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rIN/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Use system selection (default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 62c8e21..667e06a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air quality"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast info"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/arrays.xml b/packages/SettingsLib/res/values-en-rXC/arrays.xml
index 34db380..8af0a4a 100644
--- a/packages/SettingsLib/res/values-en-rXC/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rXC/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Use System Selection (Default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Use System Selection (Default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Use System Selection (Default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 21697b9..21c1bb7 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Air Quality"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Cast Info"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choose a profile picture"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Default user icon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Physical keyboard"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 27cd0ab..8821f42 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -661,6 +661,8 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info de reparto"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Control de la casa"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
+ <skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Elige una foto de perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ícono de usuario predeterminado"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-es/arrays.xml b/packages/SettingsLib/res/values-es/arrays.xml
index 0677864..4924407 100644
--- a/packages/SettingsLib/res/values-es/arrays.xml
+++ b/packages/SettingsLib/res/values-es/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Usar preferencia del sistema (predeterminado)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Usar preferencia del sistema (predeterminado)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Usar preferencia del sistema (predeterminado)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index d221c8d..cea4835 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Tiempo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calidad del aire"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de emisión"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domótica"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"De un vistazo"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Elige una imagen de perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icono de usuario predeterminado"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-et/arrays.xml b/packages/SettingsLib/res/values-et/arrays.xml
index d986ecf..0402ac2 100644
--- a/packages/SettingsLib/res/values-et/arrays.xml
+++ b/packages/SettingsLib/res/values-et/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Süsteemi valiku kasutamine (vaikeseade)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Heli: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Süsteemi valiku kasutamine (vaikeseade)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index f32ae23..13fd319 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Ilm"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Õhukvaliteet"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Osatäitjate teave"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodu juhtimine"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Ülevaade"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Valige profiilipilt"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Vaikekasutajaikoon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Füüsiline klaviatuur"</string>
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index d166e1b..9c12e95 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Erabili sistema-hautapena (lehenetsia)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Erabili sistema-hautapena (lehenetsia)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audioa"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audioa"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Erabili sistema-hautapena (lehenetsia)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index bcb57e8..3c96b6f 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Eguraldia"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Airearen kalitatea"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Igorpenari buruzko informazioa"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Etxeko gailuak kontrolatzeko aukerak"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Aukeratu profileko argazki bat"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Erabiltzaile lehenetsiaren ikonoa"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teklatu fisikoa"</string>
diff --git a/packages/SettingsLib/res/values-fa/arrays.xml b/packages/SettingsLib/res/values-fa/arrays.xml
index b7761dd..41410cb 100644
--- a/packages/SettingsLib/res/values-fa/arrays.xml
+++ b/packages/SettingsLib/res/values-fa/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"استفاده از انتخاب سیستم (پیشفرض)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"استفاده از انتخاب سیستم (پیشفرض)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"صوت <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"استفاده از انتخاب سیستم (پیشفرض)"</item>
<item msgid="8003118270854840095">"۴۴٫۱ کیلوهرتز"</item>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 6abe873..5190e0f 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"آبوهوا"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"کیفیت هوا"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"اطلاعات پخش محتوا"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"کنترل لوازم خانگی"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"انتخاب عکس نمایه"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"نماد کاربر پیشفرض"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"صفحهکلید فیزیکی"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 2f3436e..c50a2e0 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Sää"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ilmanlaatu"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Striimaustiedot"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kodin ohjaus"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Valitse profiilikuva"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Oletuskäyttäjäkuvake"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/arrays.xml b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
index 12acbb6..808c3f2 100644
--- a/packages/SettingsLib/res/values-fr-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Utiliser sélect. du système (par défaut)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Utiliser sélect. du système (par défaut)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Utiliser sélect. du système (par défaut)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 28b3cd9..b8b4659 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info diffusion"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Aperçu"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choisir une photo de profil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icône d\'utilisateur par défaut"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-fr/arrays.xml b/packages/SettingsLib/res/values-fr/arrays.xml
index 80ac7e4..92546da 100644
--- a/packages/SettingsLib/res/values-fr/arrays.xml
+++ b/packages/SettingsLib/res/values-fr/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Utiliser la sélection du système (par défaut)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Utiliser la sélection du système (par défaut)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Utiliser la sélection du système (par défaut)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index da1497a..4a2d01e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Météo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualité de l\'air"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Infos distribution"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Domotique"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Choisissez une photo de profil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icône de l\'utilisateur par défaut"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Clavier physique"</string>
diff --git a/packages/SettingsLib/res/values-gl/arrays.xml b/packages/SettingsLib/res/values-gl/arrays.xml
index b6cf48e..f663120 100644
--- a/packages/SettingsLib/res/values-gl/arrays.xml
+++ b/packages/SettingsLib/res/values-gl/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Usar selección do sistema (predeterminado)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Usa a selección do sistema (predeterminado)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Usar selección do sistema (predeterminado)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 7822749..93f9fcf 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -662,6 +662,7 @@
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Datos da emisión"</string>
<!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
<skip />
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espazo intelixente"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Escolle unha imaxe do perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icona do usuario predeterminado"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-gu/arrays.xml b/packages/SettingsLib/res/values-gu/arrays.xml
index 7e668e7..e527d81 100644
--- a/packages/SettingsLib/res/values-gu/arrays.xml
+++ b/packages/SettingsLib/res/values-gu/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ઑડિયો"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ઑડિયો"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"સિસ્ટમ પસંદગીનો ઉપયોગ કરો (ડિફૉલ્ટ)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index cebb1fe..0e19125 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"હવામાન"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"હવાની ક્વૉલિટી"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"કાસ્ટ વિશેની માહિતી"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ઘરેલુ સાધન નિયંત્રણો"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"પ્રોફાઇલ ફોટો પસંદ કરો"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ડિફૉલ્ટ વપરાશકર્તાનું આઇકન"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ભૌતિક કીબોર્ડ"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 13da75b..9b8d83e 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"सिस्टम से चुने जाने का इस्तेमाल करें (डिफ़ॉल्ट)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडियो"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 417d2b2..ed0a771 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवा की क्वालिटी"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टिंग की जानकारी"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफ़ाइल फ़ोटो चुनें"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"उपयोगकर्ता के लिए डिफ़ॉल्ट आइकॉन"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"फ़िज़िकल कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 267000f..af26040 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Vremenska prognoza"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvaliteta zraka"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Inform. o emitiranju"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Upravlj. kuć. uređ."</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Odabir profilne slike"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikona zadanog korisnika"</string>
diff --git a/packages/SettingsLib/res/values-hu/arrays.xml b/packages/SettingsLib/res/values-hu/arrays.xml
index a5f37ea..409d600 100644
--- a/packages/SettingsLib/res/values-hu/arrays.xml
+++ b/packages/SettingsLib/res/values-hu/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Rendszerérték (alapértelmezett)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Rendszerérték (alapértelmezett)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Hang: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Rendszerérték (alapértelmezett)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 69ddad8..3db43e7 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Időjárás"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Levegőminőség"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Átküldési információ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Otthonvezérlés"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Profilkép választása"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Alapértelmezett felhasználó ikonja"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fizikai billentyűzet"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index d8efbd3..8971fce 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Եղանակ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Օդի որակը"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Հեռարձակման տվյալներ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Տան կարգավորումներ"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Պրոֆիլի նկար ընտրեք"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Օգտատիրոջ կանխադրված պատկերակ"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 5b0ad98..9527417 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Gunakan Pilihan Sistem (Default)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 029ad8b..6185fd3 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualitas Udara"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info Transmisi"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrol Rumah"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Pilih foto profil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna default"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Keyboard fisik"</string>
diff --git a/packages/SettingsLib/res/values-is/arrays.xml b/packages/SettingsLib/res/values-is/arrays.xml
index 9d481f8..0b5b978 100644
--- a/packages/SettingsLib/res/values-is/arrays.xml
+++ b/packages/SettingsLib/res/values-is/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Nota val kerfisins (sjálfgefið)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Nota val kerfisins (sjálfgefið)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> hljóð"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> hljóð"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Nota val kerfisins (sjálfgefið)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ccf06b0..e718786 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Veður"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Loftgæði"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Útsendingaruppl."</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Heimastýringar"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Veldu prófílmynd"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Tákn sjálfgefins notanda"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Vélbúnaðarlyklaborð"</string>
diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml
index 62450da3..ae1e515 100644
--- a/packages/SettingsLib/res/values-it/arrays.xml
+++ b/packages/SettingsLib/res/values-it/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Usa selezione di sistema (predefinita)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Usa selezione di sistema (predefinita)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Usa selezione di sistema (predefinita)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 3811152..2e9b202 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -117,7 +117,7 @@
<string name="bluetooth_profile_opp" msgid="6692618568149493430">"Trasferimento file"</string>
<string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo di input"</string>
<string name="bluetooth_profile_pan" msgid="1006235139308318188">"Accesso a Internet"</string>
- <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivis. contatti e cronologia chiamate"</string>
+ <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Condivisione contatti e cronologia chiamate"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Usa per condivisione di contatti e cronologia chiamate"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Condivisione connessione Internet"</string>
<string name="bluetooth_profile_map" msgid="8907204701162107271">"SMS"</string>
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualità dell\'aria"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info sul cast"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlli della casa"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Scegli un\'immagine del profilo"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icona dell\'utente predefinito"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fisica"</string>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index 49f3fcf..5d72aff 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"אודיו <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"שימוש בבחירת המערכת (ברירת המחדל)"</item>
<item msgid="8003118270854840095">"44.1 קילו-הרץ"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 3421fb5..7e9fd89 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"איכות האוויר"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"פרטי ההעברה"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"בית חכם"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"בחירה של תמונת פרופיל"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"סמל המשתמש שמוגדר כברירת מחדל"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"מקלדת פיזית"</string>
diff --git a/packages/SettingsLib/res/values-ja/arrays.xml b/packages/SettingsLib/res/values-ja/arrays.xml
index d73cc43..775e31c 100644
--- a/packages/SettingsLib/res/values-ja/arrays.xml
+++ b/packages/SettingsLib/res/values-ja/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"システムの選択(デフォルト)を使用"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"システムの選択(デフォルト)を使用"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> オーディオ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> オーディオ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"システムの選択(デフォルト)を使用"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index e7d2cf8..a3311f5 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"天気"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"大気質"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"キャスト情報"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"スマートホーム"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"プロフィール写真の選択"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"デフォルト ユーザー アイコン"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"物理キーボード"</string>
diff --git a/packages/SettingsLib/res/values-ka/arrays.xml b/packages/SettingsLib/res/values-ka/arrays.xml
index c0d6f97..f3545b6 100644
--- a/packages/SettingsLib/res/values-ka/arrays.xml
+++ b/packages/SettingsLib/res/values-ka/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> აუდიო"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> აუდიო"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"სისტემის არჩეულის გამოყენება (ნაგულისხმევი)"</item>
<item msgid="8003118270854840095">"44,1 კჰც"</item>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 09f5471..b3e4402 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ჰაერის ხარისხი"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ტრანსლირების ინფო"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"სახლის მართვა"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ჭკვიანი სივრცე"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"აირჩიეთ პროფილის სურათი"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"მომხმარებლის ნაგულისხმევი ხატულა"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ფიზიკური კლავიატურა"</string>
diff --git a/packages/SettingsLib/res/values-kk/arrays.xml b/packages/SettingsLib/res/values-kk/arrays.xml
index fc998e7..9971f86 100644
--- a/packages/SettingsLib/res/values-kk/arrays.xml
+++ b/packages/SettingsLib/res/values-kk/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Жүйенің таңдағанын алу (әдепкі)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"L34C"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Жүйенің таңдағанын алу (әдепкі)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудиокодегі"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудиокодегі"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"L34C"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Жүйенің таңдағанын алу (әдепкі)"</item>
<item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 3281303..5ecba9b 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Ауа райы"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ауа сапасы"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Трансляция ақпараты"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйді басқару элементтері"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Профиль суретін таңдау"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Әдепкі пайдаланушы белгішесі"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Пернетақта"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 87c1aa2..275bcc1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"អាកាសធាតុ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"គុណភាពខ្យល់"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ព័ត៌មានអំពីការបញ្ជូន"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ការគ្រប់គ្រងផ្ទះ"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"ជ្រើសរើសរូបភាពកម្រងព័ត៌មាន"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ab437e8..7e9ff12 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"ಹವಾಮಾನ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ವಾಯು ಗುಣಮಟ್ಟ"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ಬಿತ್ತರಿಸಿದ ಮಾಹಿತಿ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ಹೋಮ್ ನಿಯಂತ್ರಣಗಳು"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"ಪ್ರೊಫೈಲ್ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ಡೀಫಾಲ್ಟ್ ಬಳಕೆದಾರರ ಐಕಾನ್"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index 7138113..16b840b 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"시스템 설정 사용(기본)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"시스템 설정 사용(기본)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 오디오"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 오디오"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"시스템 설정 사용(기본)"</item>
<item msgid="8003118270854840095">"44.1kHz"</item>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 2b8ab75..fa96ad78 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -385,7 +385,7 @@
<string name="force_msaa" msgid="4081288296137775550">"4x MSAA 강제 사용"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 앱에서 4x MSAA 사용"</string>
<string name="show_non_rect_clip" msgid="7499758654867881817">"사각형이 아닌 클립 작업 디버그"</string>
- <string name="track_frame_time" msgid="522674651937771106">"프로필 HWUI 렌더링"</string>
+ <string name="track_frame_time" msgid="522674651937771106">"HWUI 렌더링 프로파일"</string>
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"GPU 디버그 레이어 사용 설정"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"디버그 앱에 GPU 디버그 레이어 로드 허용"</string>
<string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"상세 공급업체 로깅 사용 설정"</string>
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"날씨"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"대기 상태"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"전송 정보"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"홈 컨트롤"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"프로필 사진 선택하기"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"기본 사용자 아이콘"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"물리적 키보드"</string>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 40271f7..700aae1 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"карта13"</item>
<item msgid="8147982633566548515">"карта14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
<item msgid="8003118270854840095">"44,1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e993e3f..9ebd47b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Аба ырайы"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Абанын сапаты"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Тышкы экранга чыгаруу маалыматы"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Үйдү көзөмөлдөө"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Профилдин сүрөтүн тандоо"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Демейки колдонуучунун сүрөтчөсү"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Аппараттык баскычтоп"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index d295932..11d6d43 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"ສະພາບອາກາດ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ຄຸນນະພາບອາກາດ"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ຂໍ້ມູນການສົ່ງສັນຍານ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ການຄວບຄຸມເຮືອນ"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"ເລືອກຮູບໂປຣໄຟລ໌"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string>
diff --git a/packages/SettingsLib/res/values-lt/arrays.xml b/packages/SettingsLib/res/values-lt/arrays.xml
index 946f69c..c0aafdc 100644
--- a/packages/SettingsLib/res/values-lt/arrays.xml
+++ b/packages/SettingsLib/res/values-lt/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Naudoti sistemos pasirink. (numatytasis)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Naudoti sistemos pasirink. (numatytasis)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> garsas"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> garsas"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Naudoti sistemos pasirink. (numatytasis)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index e38889b..e016956 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Oro kokybė"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Perdav. informacija"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Namų sist. valdikl."</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Pasirinkite profilio nuotrauką"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Numatytojo naudotojo piktograma"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fizinė klaviatūra"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index d899ada..ffd41635 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Laikapstākļi"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Gaisa kvalitāte"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Apraides informācija"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Mājas kontrolierīces"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Profila attēla izvēle"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Noklusējuma lietotāja ikona"</string>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 9c46f21..41427c1 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Користи избор на системот (стандардно)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Користи избор на системот (стандардно)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Користи избор на системот (стандардно)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index c8033e3..26a87e3 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет на воздух"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Инфо за улогите"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Контроли за домот"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Изберете профилна слика"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Икона за стандарден корисник"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-ml/arrays.xml b/packages/SettingsLib/res/values-ml/arrays.xml
index 4715e2a..98e3bd6 100644
--- a/packages/SettingsLib/res/values-ml/arrays.xml
+++ b/packages/SettingsLib/res/values-ml/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ഓഡിയോ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ഓഡിയോ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"സിസ്റ്റം സെലക്ഷൻ ഉപയോഗിക്കൂ (ഡിഫോൾട്ട്)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 089f6af..5d9f799 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"വായു നിലവാരം"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"കാസ്റ്റ് വിവരങ്ങൾ"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ഹോം കൺട്രോളുകൾ"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"പ്രൊഫൈൽ ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ഡിഫോൾട്ട് ഉപയോക്തൃ ഐക്കൺ"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ഫിസിക്കൽ കീബോർഡ്"</string>
diff --git a/packages/SettingsLib/res/values-mn/arrays.xml b/packages/SettingsLib/res/values-mn/arrays.xml
index 63fa53b..f3c10d7 100644
--- a/packages/SettingsLib/res/values-mn/arrays.xml
+++ b/packages/SettingsLib/res/values-mn/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Системийн сонголтыг ашиглах (Өгөгдмөл)"</item>
<item msgid="8003118270854840095">"44.1 кГц"</item>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index cc35053..df6fa89 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -447,8 +447,8 @@
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Дьютераномаль (улаан-ногоон)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгөний засвар нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:<br/> <ol> <li>&nbsp;Өнгөнүүдийг илүү нарийвчилж харах</li> <li>&nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах</li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулга"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Өнгө тохируулга нь таныг дараахыг хийхийг хүсэх үед хэрэгтэй байж болно:<br/> <ol> <li>&nbsp;Өнгөнүүдийг илүү нарийвчилж харах</li> <li>&nbsp;Төвлөрөхийн тулд өнгөнүүдийг хасах</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string>
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Агаарын чанар"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Дамжуулах мэдээлэл"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Гэрийн удирдлага"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Профайл зураг сонгох"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Өгөгдмөл хэрэглэгчийн дүрс тэмдэг"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Биет гар"</string>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index a54f990..c37baaa2 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"सिस्टीम निवड वापरा (डीफॉल्ट)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडिओ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ऑडिओ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"सिस्टम निवड वापरा (डीफॉल्ट)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 1907d00..4597549 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"हवेची गुणवत्ता"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसंबंधित माहिती"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"होम कंट्रोल"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"स्मार्टस्पेस"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो निवडा"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"डीफॉल्ट वापरकर्ता आयकन"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"वास्तविक कीबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-ms/arrays.xml b/packages/SettingsLib/res/values-ms/arrays.xml
index b26ed23..b19f038 100644
--- a/packages/SettingsLib/res/values-ms/arrays.xml
+++ b/packages/SettingsLib/res/values-ms/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Gunakan Pilihan Sistem (Lalai)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Gunakan Pilihan Sistem (Lalai)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Gunakan Pilihan Sistem (Lalai)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index b3929c9..1471040 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Cuaca"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kualiti Udara"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maklumat Pelakon"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kawalan Rumah"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Pilih gambar profil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikon pengguna lalai"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Papan kekunci fizikal"</string>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index ed95dfe..3398c5b 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> အသံ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> အသံ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"စနစ်ရွေးချယ်မှုကို အသုံးပြုပါ (မူရင်း)"</item>
<item msgid="8003118270854840095">"၄၄.၁ kHz"</item>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f7a33a9..ee5601f 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"မိုးလေဝသ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"လေထုအရည်အသွေး"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ကာစ် အချက်အလက်"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"အိမ်သတ်မှတ်ချက်များ"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"ပရိုဖိုင်ပုံ ရွေးပါ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"မူရင်းအသုံးပြုသူ သင်္ကေတ"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ပကတိ ကီးဘုတ်"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 317c2db..7e65fa0 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Bruk systemvalg (standard)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Bruk systemvalg (standard)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-lyd"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-lyd"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Bruk systemvalg (standard)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9b99631..a8fd256 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Vær"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformasjon"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hjemkontroller"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Velg et profilbilde"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Standard brukerikon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fysisk tastatur"</string>
diff --git a/packages/SettingsLib/res/values-ne/arrays.xml b/packages/SettingsLib/res/values-ne/arrays.xml
index b3c3ee2..ac1f187 100644
--- a/packages/SettingsLib/res/values-ne/arrays.xml
+++ b/packages/SettingsLib/res/values-ne/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> अडियो"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> अडियो"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"सिस्टमको छनौट प्रयोग गरियोस् (डिफल्ट)"</item>
<item msgid="8003118270854840095">"४४.१ kHz"</item>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1534cba..17b81b2 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"मौसम"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"वायुको गुणस्तर"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"कास्टसम्बन्धी जानकारी"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"घरायसी उपकरणका नियन्त्रण"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"प्रोफाइल फोटो छान्नुहोस्"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"प्रयोगकर्ताको डिफल्ट आइकन"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"भौतिक किबोर्ड"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index e809452..7c90eab 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Systeemselectie gebruiken (standaard)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Gebruik systeemselectie (standaard)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Systeemselectie gebruiken (standaard)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index ba11b46..1251ed9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luchtkwaliteit"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Castinformatie"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Bediening voor in huis"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Kies een profielfoto"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Standaard gebruikersicoon"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiek toetsenbord"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 09fa59d..715f1eb 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -116,12 +116,12 @@
<string name="bluetooth_profile_headset" msgid="5395952236133499331">"ଫୋନ୍ କଲ୍ଗୁଡ଼ିକ"</string>
<string name="bluetooth_profile_opp" msgid="6692618568149493430">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍"</string>
<string name="bluetooth_profile_hid" msgid="2969922922664315866">"ଇନ୍ପୁଟ୍ ଡିଭାଇସ୍"</string>
- <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍"</string>
+ <string name="bluetooth_profile_pan" msgid="1006235139308318188">"ଇଣ୍ଟରନେଟ ଆକ୍ସେସ"</string>
<string name="bluetooth_profile_pbap" msgid="4262303387989406171">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"କଣ୍ଟାକ୍ଟ ଏବଂ କଲ ଇତିହାସ ସେୟାରିଂ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ଶେୟାରିଙ୍ଗ"</string>
<string name="bluetooth_profile_map" msgid="8907204701162107271">"ଟେକ୍ସଟ୍ ମେସେଜ୍"</string>
- <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ୍"</string>
+ <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
@@ -156,7 +156,7 @@
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ଦ୍ୱାରା ପେୟାରିଙ୍ଗ ପାଇଁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରିଦିଆଗଲା।"</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"କମ୍ପ୍ୟୁଟର୍"</string>
<string name="bluetooth_talkback_headset" msgid="3406852564400882682">"ହେଡ୍ସେଟ୍"</string>
- <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ୍"</string>
+ <string name="bluetooth_talkback_phone" msgid="868393783858123880">"ଫୋନ"</string>
<string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"ଇମେଜିଙ୍ଗ"</string>
<string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"ହେଡ୍ଫୋନ୍"</string>
<string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"ଇନ୍ପୁଟ୍ ଉପକରଣ"</string>
@@ -246,7 +246,7 @@
<string name="adb_pair_method_code_summary" msgid="6370414511333685185">"ଛଅ ଡିଜିଟ୍ କୋଡ୍ ବ୍ୟବହାର କରି ନୂଆ ଡିଭାଇସଗୁଡ଼ିକୁ ପେୟାର୍ କରନ୍ତୁ"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"ପେୟାର୍ ହୋଇଥିବା ଡିଭାଇସଗୁଡ଼ିକ"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"ବର୍ତ୍ତମାନ ସଂଯୁକ୍ତ ଅଛି"</string>
- <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସ୍ ବିବରଣୀ"</string>
+ <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"ଡିଭାଇସର ବିବରଣୀ"</string>
<string name="adb_device_forget" msgid="193072400783068417">"ଭୁଲିଯାଆନ୍ତୁ"</string>
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"ଡିଭାଇସ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟ: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"ସଂଯୋଗ ବିଫଳ ହେଲା"</string>
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"ପାଣିପାଗ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ବାୟୁର ଗୁଣବତ୍ତା"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"କାଷ୍ଟ ସୂଚନା"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ହୋମ କଣ୍ଟ୍ରୋଲ"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ଡିଫଲ୍ଟ ଉପଯୋଗକର୍ତ୍ତା ଆଇକନ"</string>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index a3fae22..0fd5c56 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ਆਡੀਓ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ਆਡੀਓ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"ਸਿਸਟਮ ਚੋਣ ਦੀ ਵਰਤੋਂ ਕਰੋ (ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9df0fef..51bf22d 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"ਮੌਸਮ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ਹਵਾ ਦੀ ਕੁਆਲਿਟੀ"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ਕਾਸਟ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ਹੋਮ ਕੰਟਰੋਲ"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਤਸਵੀਰ ਚੁਣੋ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਰਤੋਂਕਾਰ ਪ੍ਰਤੀਕ"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4599fd..64cbe78 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Pogoda"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Jakość powietrza"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Obsada"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Sterowanie domem"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Wybierz zdjęcie profilowe"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikona domyślnego użytkownika"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/arrays.xml b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt-rBR/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -208,7 +208,7 @@
<string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
<string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
<string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
- <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+ <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
<string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
<string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
<string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade do ar"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/arrays.xml b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
index 985bd51..e323455 100644
--- a/packages/SettingsLib/res/values-pt-rPT/arrays.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Utilizar seleção do sistema (predefinido)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Utilizar seleção do sistema (predefinido)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Utilizar seleção do sistema (predefinido)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 4988136..ed3f642 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade do ar"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ctr. domésticos"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Espaço inteligente"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Escolha uma imagem do perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ícone do utilizador predefinido"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-pt/arrays.xml b/packages/SettingsLib/res/values-pt/arrays.xml
index 1883ef3..f218fab 100644
--- a/packages/SettingsLib/res/values-pt/arrays.xml
+++ b/packages/SettingsLib/res/values-pt/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Usar seleção do sistema (padrão)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Usar seleção do sistema (padrão)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Áudio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Usar seleção do sistema (padrão)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 12507c5..531bc63 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -208,7 +208,7 @@
<string name="tts_engine_settings_title" msgid="7849477533103566291">"Configurações para <xliff:g id="TTS_ENGINE_NAME">%s</xliff:g>"</string>
<string name="tts_engine_settings_button" msgid="477155276199968948">"Iniciar configurações do mecanismo"</string>
<string name="tts_engine_preference_section_title" msgid="3861562305498624904">"Mecanismo preferencial"</string>
- <string name="tts_general_section_title" msgid="8919671529502364567">"Gerais"</string>
+ <string name="tts_general_section_title" msgid="8919671529502364567">"Geral"</string>
<string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"Redefinir o tom de voz"</string>
<string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"Redefinir o tom de voz para o padrão."</string>
<string-array name="tts_rate_entries">
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Clima"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Qualidade do ar"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info. de transmissão"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Automação residencial"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Escolher a foto do perfil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ícone de usuário padrão"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Teclado físico"</string>
diff --git a/packages/SettingsLib/res/values-ro/arrays.xml b/packages/SettingsLib/res/values-ro/arrays.xml
index f1e9986..34b0ac9 100644
--- a/packages/SettingsLib/res/values-ro/arrays.xml
+++ b/packages/SettingsLib/res/values-ro/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Folosiți selectarea sistemului (prestabilit)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Folosiți selectarea sistemului (prestabilit)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audio <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Folosiți selectarea sistemului (prestabilit)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f49fcdd..8f87a7c 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Meteo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Calitatea aerului"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informații artiști"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Controlul locuinței"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Alegeți o fotografie de profil"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Pictograma prestabilită a utilizatorului"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Tastatură fizică"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 38930a5..e2f05dc 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Качество воздуха"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Данные о трансляции"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизация дома"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Выберите фото профиля"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Значок пользователя по умолчанию"</string>
diff --git a/packages/SettingsLib/res/values-si/arrays.xml b/packages/SettingsLib/res/values-si/arrays.xml
index 8386c1a..eaacfb8 100644
--- a/packages/SettingsLib/res/values-si/arrays.xml
+++ b/packages/SettingsLib/res/values-si/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්රව්යය"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්රව්යය"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ශ්රව්යය"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ශ්රව්යය"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"පද්ධති තේරීම භාවිත කරන්න (පෙරනිමි)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index fa0296f..1d52e0b 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"කාලගුණය"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"වායු ගුණත්වය"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"විකාශ තතු"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"නිවෙස් පාලන"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"ස්මාර්ට් අවකාශය"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"පැතිකඩ පින්තූරයක් තේරීම"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"පෙරනිමි පරිශීලක නිරූපකය"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"භෞතික යතුරු පුවරුව"</string>
diff --git a/packages/SettingsLib/res/values-sk/arrays.xml b/packages/SettingsLib/res/values-sk/arrays.xml
index 370b23f..bbfe969 100644
--- a/packages/SettingsLib/res/values-sk/arrays.xml
+++ b/packages/SettingsLib/res/values-sk/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Použiť voľbu systému (predvolené)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Použiť voľbu systému (predvolené)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Zvuk: <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Použiť voľbu systému (predvolené)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a7c69c8..cf501cd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kvalita vzduchu"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Informácie o prenose"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ovládanie domácnosti"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Výber profilovej fotky"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Predvolená ikona používateľa"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fyzická klávesnica"</string>
diff --git a/packages/SettingsLib/res/values-sl/arrays.xml b/packages/SettingsLib/res/values-sl/arrays.xml
index 6e33e38..b2003e5 100644
--- a/packages/SettingsLib/res/values-sl/arrays.xml
+++ b/packages/SettingsLib/res/values-sl/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Uporabi sistemsko izbiro (privzeto)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Uporabi sistemsko izbiro (privzeto)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Zvok <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Uporabi sistemsko izbiro (privzeto)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 7334cea..bc89f5a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kakovost zraka"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"O zasedbi"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Nadzor doma"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Hitri pregled"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Izbira profilne slike"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Privzeta ikona uporabnika"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fizična tipkovnica"</string>
diff --git a/packages/SettingsLib/res/values-sq/arrays.xml b/packages/SettingsLib/res/values-sq/arrays.xml
index 8a6d853..ed86380 100644
--- a/packages/SettingsLib/res/values-sq/arrays.xml
+++ b/packages/SettingsLib/res/values-sq/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Audioja e <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Përdor përzgjedhjen e sistemit (e parazgjedhur)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 59359cdf..e7af25d 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Moti"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Cilësia e ajrit"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Të dhënat e aktorëve"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Kontrollet e shtëpisë"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Zgjidh një fotografi profili"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikona e parazgjedhur e përdoruesit"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Tastiera fizike"</string>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index 7e198bf..a95e47b 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Користи избор система (подразумевано)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Користи избор система (подразумевано)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> аудио"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Користи избор система (подразумевано)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index eee3f89..a02d674 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Време"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Квалитет ваздуха"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Подаци о пребацивању"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Управљање домом"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Одаберите слику профила"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Подразумевана икона корисника"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Физичка тастатура"</string>
diff --git a/packages/SettingsLib/res/values-sv/arrays.xml b/packages/SettingsLib/res/values-sv/arrays.xml
index f99a85b..c63465c 100644
--- a/packages/SettingsLib/res/values-sv/arrays.xml
+++ b/packages/SettingsLib/res/values-sv/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Använd systemval (standardinställning)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Använd systemval (standardinställning)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>-ljud"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>-ljud"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Använd systemval (standardinställning)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1a8815c..e135d1b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Väder"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Luftkvalitet"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Info om rollistan"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Hemstyrning"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Välj en profilbild"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Ikon för standardanvändare"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fysiskt tangentbord"</string>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index dab4279..53dc6e5 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"ramani ya 13"</item>
<item msgid="8147982633566548515">"ramani ya 14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Sauti ya <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Tumia Uteuzi wa Mfumo (Chaguomsingi)"</item>
<item msgid="8003118270854840095">"kHz 44.1"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 5f1d141..a900064 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Hali ya Hewa"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ubora wa Hewa"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Maelezo ya Wahusika"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Udhibiti wa Vifaa Nyumbani"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Chagua picha ya wasifu"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Aikoni chaguomsingi ya mtumiaji"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Kibodi halisi"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index a0f1fa6..957bd61 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ஆடியோ"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ஆடியோ"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"சாதனத் தேர்வைப் பயன்படுத்து (இயல்பு)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index b8c37b0..645278c 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"வானிலை"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"காற்றின் தரம்"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"அலைபரப்புத் தகவல்"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ஹோம் கன்ட்ரோல்கள்"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"சுயவிவரப் படத்தைத் தேர்வுசெய்யுங்கள்"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"இயல்புநிலைப் பயனர் ஐகான்"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"கீபோர்டு"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 18a2758..d4361e5 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ఆడియో"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ఆడియో"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"సిస్టమ్ ఎంపికను ఉపయోగించండి (ఆటోమేటిక్)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 504c827..de3f390 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"గాలి క్వాలిటీ"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"కాస్ట్ సమాచారం"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"హోమ్ కంట్రోల్స్"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"స్మార్ట్స్పేస్"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"ప్రొఫైల్ ఫోటోను ఎంచుకోండి"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ఆటోమేటిక్ సెట్టింగ్ యూజర్ చిహ్నం"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"భౌతిక కీబోర్డ్"</string>
diff --git a/packages/SettingsLib/res/values-th/arrays.xml b/packages/SettingsLib/res/values-th/arrays.xml
index 04a5f4d..782e95e 100644
--- a/packages/SettingsLib/res/values-th/arrays.xml
+++ b/packages/SettingsLib/res/values-th/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"เสียง <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"ใช้การเลือกของระบบ (ค่าเริ่มต้น)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index f33630d..dfb87a8 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"สภาพอากาศ"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"คุณภาพอากาศ"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"ข้อมูลแคสต์"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ระบบควบคุมอุปกรณ์ในบ้าน"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"เลือกรูปโปรไฟล์"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ไอคอนผู้ใช้เริ่มต้น"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"แป้นพิมพ์จริง"</string>
diff --git a/packages/SettingsLib/res/values-tl/arrays.xml b/packages/SettingsLib/res/values-tl/arrays.xml
index 59cb1f3..19d3423 100644
--- a/packages/SettingsLib/res/values-tl/arrays.xml
+++ b/packages/SettingsLib/res/values-tl/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Gamitin ang Pagpili ng System (Default)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Gamitin ang Pagpili ng System (Default)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> na audio"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> na audio"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Gamitin ang Pagpili ng System (Default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f95f0e50..fdaece1 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Lagay ng Panahon"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Kalidad ng Hangin"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Impormasyon ng Cast"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Home Controls"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Pumili ng larawan sa profile"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Icon ng default na user"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Pisikal na keyboard"</string>
diff --git a/packages/SettingsLib/res/values-tr/arrays.xml b/packages/SettingsLib/res/values-tr/arrays.xml
index 5ed35fa..37891ae 100644
--- a/packages/SettingsLib/res/values-tr/arrays.xml
+++ b/packages/SettingsLib/res/values-tr/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Sistem Seçimini Kullan (Varsayılan)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Sistem Seçimini Kullan (Varsayılan)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ses"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> ses"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Sistem Seçimini Kullan (Varsayılan)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 2fa2bd1..a2eb0e2 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Hava durumu"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Hava Kalitesi"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayın Bilgisi"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Ev Kontrolleri"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Profil fotoğrafı seçin"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Varsayılan kullanıcı simgesi"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziksel klavye"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 9be0855..63d7118 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Погода"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Якість повітря"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Акторський склад"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Автоматизація дому"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Виберіть зображення профілю"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Значок користувача за умовчанням"</string>
diff --git a/packages/SettingsLib/res/values-ur/arrays.xml b/packages/SettingsLib/res/values-ur/arrays.xml
index d097458..db9941e 100644
--- a/packages/SettingsLib/res/values-ur/arrays.xml
+++ b/packages/SettingsLib/res/values-ur/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> آڈیو"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> آڈیو"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"سسٹم انتخاب کا استعمال کریں (ڈیفالٹ)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 8eb9a11..2de47ec 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -661,6 +661,7 @@
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"ہوا کا معیار"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"کاسٹ کرنے کی معلومات"</string>
<string name="dream_complication_title_home_controls" msgid="9153381632476738811">"ہوم کنٹرولز"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"پروفائل کی تصویر منتخب کریں"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"ڈیفالٹ صارف کا آئیکن"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"فزیکل کی بورڈ"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index a5c8683..707a54e 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -660,7 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Ob-havo"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Havo sifati"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Translatsiya axboroti"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Uy boshqaruvi"</string>
+ <!-- no translation found for dream_complication_title_smartspace (4197829945636051120) -->
<skip />
<string name="avatar_picker_title" msgid="8492884172713170652">"Profil rasmini tanlash"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Foydalanuvchining standart belgisi"</string>
diff --git a/packages/SettingsLib/res/values-vi/arrays.xml b/packages/SettingsLib/res/values-vi/arrays.xml
index 31867e2..ee599d6 100644
--- a/packages/SettingsLib/res/values-vi/arrays.xml
+++ b/packages/SettingsLib/res/values-vi/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="2908219194098827570">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g>"</item>
+ <item msgid="3517061573669307965">"Âm thanh <xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g>"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Sử dụng lựa chọn của hệ thống (Mặc định)"</item>
<item msgid="8003118270854840095">"44,1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 45a0465..82eb63d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Thời tiết"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Chất lượng không khí"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Thông tin về dàn nghệ sĩ"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Điều khiển nhà"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Chọn một ảnh hồ sơ"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Biểu tượng người dùng mặc định"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Bàn phím thực"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 973d7d0..2a85d31 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"使用系统选择(默认)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"使用系统选择(默认)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音频"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音频"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"使用系统选择(默认)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 2bc8a30..9f8007b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"天气"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"空气质量"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放信息"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"家居控制"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"SmartSpace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"选择个人资料照片"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"默认用户图标"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/arrays.xml b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
index 87f3825..a84f0e2 100644
--- a/packages/SettingsLib/res/values-zh-rHK/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"使用系統選擇 (預設)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"使用系統選擇 (預設)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"使用系統選擇 (預設)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 114bc64..cc80ebc 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣質素"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"投放資料"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"智能家居"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人檔案相片"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index 529287f..66aaa56b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"map13"</item>
<item msgid="8147982633566548515">"map14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"系統自動選擇 (預設)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+ <item msgid="3825367753087348007">"LDAC"</item>
+ <item msgid="328951785723550863">"LC3"</item>
+ <item msgid="506175145534048710">"Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"系統自動選擇 (預設)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> 音訊"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> 音訊"</item>
+ <item msgid="2553206901068987657">"LDAC"</item>
+ <item msgid="3940992993241040716">"LC3"</item>
+ <item msgid="7940970833006181407">"Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"系統自動選擇 (預設)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index a7ae213..46aaef8 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"天氣"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"空氣品質"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"演出者資訊"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"居家控制系統"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"智慧空間"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"選擇個人資料相片"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"預設使用者圖示"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"實體鍵盤"</string>
diff --git a/packages/SettingsLib/res/values-zu/arrays.xml b/packages/SettingsLib/res/values-zu/arrays.xml
index 59ead86..0494f1c 100644
--- a/packages/SettingsLib/res/values-zu/arrays.xml
+++ b/packages/SettingsLib/res/values-zu/arrays.xml
@@ -85,10 +85,26 @@
<item msgid="7073042887003102964">"Imephu13"</item>
<item msgid="8147982633566548515">"Imephu14"</item>
</string-array>
- <!-- no translation found for bluetooth_a2dp_codec_titles:6 (328951785723550863) -->
- <!-- no translation found for bluetooth_a2dp_codec_titles:7 (506175145534048710) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:6 (3940992993241040716) -->
- <!-- no translation found for bluetooth_a2dp_codec_summaries:7 (7940970833006181407) -->
+ <string-array name="bluetooth_a2dp_codec_titles">
+ <item msgid="2494959071796102843">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+ <item msgid="4055460186095649420">"SBC"</item>
+ <item msgid="720249083677397051">"I-AAC"</item>
+ <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+ <item msgid="2908219194098827570">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+ <item msgid="3825367753087348007">"I-LDAC"</item>
+ <item msgid="328951785723550863">"I-LC3"</item>
+ <item msgid="506175145534048710">"I-Opus"</item>
+ </string-array>
+ <string-array name="bluetooth_a2dp_codec_summaries">
+ <item msgid="8868109554557331312">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
+ <item msgid="9024885861221697796">"SBC"</item>
+ <item msgid="4688890470703790013">"I-AAC"</item>
+ <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> umsindo"</item>
+ <item msgid="3517061573669307965">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX_HD">aptX™ HD</xliff:g> umsindo"</item>
+ <item msgid="2553206901068987657">"I-LDAC"</item>
+ <item msgid="3940992993241040716">"I-LC3"</item>
+ <item msgid="7940970833006181407">"I-Opus"</item>
+ </string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item msgid="926809261293414607">"Sebenzisa ukukhetha kwesistimu (Okuzenzakalelayo)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e037d12..e788357 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -660,8 +660,8 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Isimo sezulu"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Ikhwalithi Yomoya"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Ulwazi Lokusakaza"</string>
- <!-- no translation found for dream_complication_title_home_controls (9153381632476738811) -->
- <skip />
+ <string name="dream_complication_title_home_controls" msgid="9153381632476738811">"Izilawuli Zasekhaya"</string>
+ <string name="dream_complication_title_smartspace" msgid="4197829945636051120">"I-Smartspace"</string>
<string name="avatar_picker_title" msgid="8492884172713170652">"Khetha isithombe sephrofayela"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Isithonjana somsebenzisi sokuzenzakalelayo"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Ikhibhodi ephathekayo"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b879223..11cb9c1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -962,6 +962,9 @@
<!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
<string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
+ <!-- UI debug setting: enable desktop mode [CHAR LIMIT=25] -->
+ <string name="desktop_mode">Desktop mode</string>
+
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
<!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index cf23a54..7913c16 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -792,10 +792,8 @@
// Rebuilding of app list. Synchronized on mRebuildSync.
final Object mRebuildSync = new Object();
boolean mRebuildRequested;
- boolean mRebuildAsync;
AppFilter mRebuildFilter;
Comparator<AppEntry> mRebuildComparator;
- ArrayList<AppEntry> mRebuildResult;
ArrayList<AppEntry> mLastAppList;
boolean mRebuildForeground;
@@ -901,11 +899,9 @@
synchronized (mRebuildingSessions) {
mRebuildingSessions.add(this);
mRebuildRequested = true;
- mRebuildAsync = true;
mRebuildFilter = filter;
mRebuildComparator = comparator;
mRebuildForeground = foreground;
- mRebuildResult = null;
if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
Message msg = mBackgroundHandler.obtainMessage(
BackgroundHandler.MSG_REBUILD_LIST);
@@ -985,15 +981,10 @@
synchronized (mRebuildSync) {
if (!mRebuildRequested) {
mLastAppList = filteredApps;
- if (!mRebuildAsync) {
- mRebuildResult = filteredApps;
- mRebuildSync.notifyAll();
- } else {
- if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
- Message msg = mMainHandler.obtainMessage(
- MainHandler.MSG_REBUILD_COMPLETE, this);
- mMainHandler.sendMessage(msg);
- }
+ if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
+ Message msg = mMainHandler.obtainMessage(
+ MainHandler.MSG_REBUILD_COMPLETE, this);
+ mMainHandler.sendMessage(msg);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
index c7eb682..fb3f382 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -27,7 +27,6 @@
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -76,14 +75,22 @@
private static final String LIBRARY_TAIL_STRING = "</ul>\n<strong>Files</strong>";
private static final String FILES_HEAD_STRING = "<ul class=\"files\">";
+ private static final String FILES_TAIL_STRING = "</ul>\n</div><!-- table of contents -->";
- private static final String HTML_MIDDLE_STRING =
- "</ul>\n"
- + "</div><!-- table of contents -->\n"
- + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+ private static final String CONTENT_HEAD_STRING =
+ "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+ private static final String CONTENT_TAIL_STRING = "</table>";
- private static final String HTML_REAR_STRING =
- "</table></body></html>";
+ private static final String IMAGES_HEAD_STRING =
+ "<div class=\"images-list\"><strong>Images</strong>\n<ul class=\"images\">";
+ private static final String IMAGES_TAIL_STRING = "</ul></div>\n";
+
+ private static final String PATH_COUNTS_HEAD_STRING =
+ "<div class=\"path-counts\"><table>\n <tr><th>Path prefix</th><th>Count</th></tr>\n";
+ private static final String PATH_COUNTS_TAIL_STRING = "</table></div>\n";
+
+ private static final String HTML_TAIL_STRING =
+ "</body></html>";
private final List<File> mXmlFiles;
@@ -137,13 +144,13 @@
try {
writer = new PrintWriter(outputFile);
- generateHtml(mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap, writer,
- noticeHeader);
+ generateHtml(mXmlFiles, mFileNameToLibraryToContentIdMap, mContentIdToFileContentMap,
+ writer, noticeHeader);
writer.flush();
writer.close();
return true;
- } catch (FileNotFoundException | SecurityException e) {
+ } catch (IOException | SecurityException e) {
Log.e(TAG, "Failed to generate " + outputFile, e);
if (writer != null) {
@@ -271,14 +278,33 @@
return result.toString();
}
+ private static String pathPrefix(String path) {
+ String prefix = path;
+ while (prefix.length() > 0 && prefix.substring(0, 1).equals("/")) {
+ prefix = prefix.substring(1);
+ }
+ int idx = prefix.indexOf("/");
+ if (idx > 0) {
+ prefix = prefix.substring(0, idx);
+ }
+ return prefix;
+ }
+
@VisibleForTesting
- static void generateHtml(Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
+ static void generateHtml(List<File> xmlFiles,
+ Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap,
Map<String, String> contentIdToFileContentMap, PrintWriter writer,
- String noticeHeader) {
+ String noticeHeader) throws IOException {
List<String> fileNameList = new ArrayList();
fileNameList.addAll(fileNameToLibraryToContentIdMap.keySet());
Collections.sort(fileNameList);
+ SortedMap<String, Integer> prefixToCount = new TreeMap();
+ for (String f : fileNameList) {
+ String prefix = pathPrefix(f);
+ prefixToCount.merge(prefix, 1, Integer::sum);
+ }
+
SortedMap<String, Set<String>> libraryToContentIdMap = new TreeMap();
for (Map<String, Set<String>> libraryToContentValue :
fileNameToLibraryToContentIdMap.values()) {
@@ -324,70 +350,95 @@
writer.println(LIBRARY_TAIL_STRING);
}
- writer.println(FILES_HEAD_STRING);
-
- // Prints all the file list with a link to its license file content.
- for (String fileName : fileNameList) {
- for (Map.Entry<String, Set<String>> libToContentId :
- fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
- String libraryName = libToContentId.getKey();
- if (libraryName == null) {
- libraryName = "";
- }
- for (String contentId : libToContentId.getValue()) {
- // Assigns an id to a newly referred license file content.
- if (!contentIdToOrderMap.containsKey(contentId)) {
- contentIdToOrderMap.put(contentId, count);
-
- // An index in contentIdAndFileNamesList is the order of each element.
- contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
- count++;
+ if (!fileNameList.isEmpty()) {
+ writer.println(FILES_HEAD_STRING);
+ // Prints all the file list with a link to its license file content.
+ for (String fileName : fileNameList) {
+ for (Map.Entry<String, Set<String>> libToContentId :
+ fileNameToLibraryToContentIdMap.get(fileName).entrySet()) {
+ String libraryName = libToContentId.getKey();
+ if (libraryName == null) {
+ libraryName = "";
}
+ for (String contentId : libToContentId.getValue()) {
+ // Assigns an id to a newly referred license file content.
+ if (!contentIdToOrderMap.containsKey(contentId)) {
+ contentIdToOrderMap.put(contentId, count);
- int id = contentIdToOrderMap.get(contentId);
- ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
- List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
- libraryName, k -> new ArrayList());
- files.add(fileName);
+ // An index in contentIdAndFileNamesList is the order of each element.
+ contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+ count++;
+ }
+
+ int id = contentIdToOrderMap.get(contentId);
+ ContentIdAndFileNames elem = contentIdAndFileNamesList.get(id);
+ List<String> files = elem.mLibraryToFileNameMap.computeIfAbsent(
+ libraryName, k -> new ArrayList());
+ files.add(fileName);
+ if (TextUtils.isEmpty(libraryName)) {
+ writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+ } else {
+ writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
+ id, fileName, libraryName);
+ }
+ }
+ }
+ }
+ writer.println(FILES_TAIL_STRING);
+ }
+
+ if (!contentIdAndFileNamesList.isEmpty()) {
+ writer.println(CONTENT_HEAD_STRING);
+ // Prints all contents of the license files in order of id.
+ for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
+ // Assigns an id to a newly referred license file content (should never happen here)
+ if (!contentIdToOrderMap.containsKey(contentIdAndFileNames.mContentId)) {
+ contentIdToOrderMap.put(contentIdAndFileNames.mContentId, count);
+ count++;
+ }
+ int id = contentIdToOrderMap.get(contentIdAndFileNames.mContentId);
+ writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", id);
+ for (Map.Entry<String, List<String>> libraryFiles :
+ contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
+ String libraryName = libraryFiles.getKey();
if (TextUtils.isEmpty(libraryName)) {
- writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+ writer.println("<div class=\"label\">Notices for file(s):</div>");
} else {
- writer.format("<li><a href=\"#id%d\">%s - %s</a></li>\n",
- id, fileName, libraryName);
+ writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
+ libraryName);
}
+ writer.println("<div class=\"file-list\">");
+ for (String fileName : libraryFiles.getValue()) {
+ writer.format("%s <br/>\n", fileName);
+ }
+ writer.println("</div><!-- file-list -->");
}
+ writer.println("<pre class=\"license-text\">");
+ writer.println(contentIdToFileContentMap.get(
+ contentIdAndFileNames.mContentId));
+ writer.println("</pre><!-- license-text -->");
+ writer.println("</td></tr><!-- same-license -->");
}
+ writer.println(CONTENT_TAIL_STRING);
}
- writer.println(HTML_MIDDLE_STRING);
-
- count = 0;
- // Prints all contents of the license files in order of id.
- for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
- writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
- for (Map.Entry<String, List<String>> libraryFiles :
- contentIdAndFileNames.mLibraryToFileNameMap.entrySet()) {
- String libraryName = libraryFiles.getKey();
- if (TextUtils.isEmpty(libraryName)) {
- writer.println("<div class=\"label\">Notices for file(s):</div>");
- } else {
- writer.format("<div class=\"label\"><strong>%s</strong> used by:</div>\n",
- libraryName);
- }
- writer.println("<div class=\"file-list\">");
- for (String fileName : libraryFiles.getValue()) {
- writer.format("%s <br/>\n", fileName);
- }
- writer.println("</div><!-- file-list -->");
- count++;
+ if (!xmlFiles.isEmpty()) {
+ writer.println(IMAGES_HEAD_STRING);
+ for (File file : xmlFiles) {
+ writer.format(" <li>%s</li>\n", pathPrefix(file.getCanonicalPath()));
}
- writer.println("<pre class=\"license-text\">");
- writer.println(contentIdToFileContentMap.get(
- contentIdAndFileNames.mContentId));
- writer.println("</pre><!-- license-text -->");
- writer.println("</td></tr><!-- same-license -->");
+ writer.println(IMAGES_TAIL_STRING);
}
- writer.println(HTML_REAR_STRING);
+ if (!prefixToCount.isEmpty()) {
+ writer.println(PATH_COUNTS_HEAD_STRING);
+ for (Map.Entry<String, Integer> entry : prefixToCount.entrySet()) {
+ writer.format(" <tr><td>%s</td><td>%d</td></tr>\n",
+ entry.getKey(), entry.getValue());
+ }
+ writer.println(PATH_COUNTS_TAIL_STRING);
+ }
+
+ writer.println(HTML_TAIL_STRING);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index 09b0d7f..fe337d267 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -24,13 +24,16 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -116,6 +119,7 @@
+ "<li><a href=\"#id0\">/file0 - libA</a></li>\n"
+ "<li><a href=\"#id1\">/file0 - libB</a></li>\n"
+ "<li><a href=\"#id0\">/file1 - libA</a></li>\n"
+ + "<li><a href=\"#id0\">/file2 - libC</a></li>\n"
+ "</ul>\n"
+ "</div><!-- table of contents -->\n"
+ "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
@@ -125,6 +129,10 @@
+ "/file0 <br/>\n"
+ "/file1 <br/>\n"
+ "</div><!-- file-list -->\n"
+ + "<div class=\"label\"><strong>libC</strong> used by:</div>\n"
+ + "<div class=\"file-list\">\n"
+ + "/file2 <br/>\n"
+ + "</div><!-- file-list -->\n"
+ "<pre class=\"license-text\">\n"
+ "license content #0\n"
+ "</pre><!-- license-text -->\n"
@@ -197,7 +205,8 @@
}
@Test
- public void testGenerateHtml() {
+ public void testGenerateHtml() throws Exception {
+ List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
Map<String, Set<String>> toBoth = new HashMap<>();
@@ -213,43 +222,48 @@
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
new PrintWriter(output), "");
assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING);
}
@Test
- public void testGenerateNewHtml() {
+ public void testGenerateNewHtml() throws Exception {
+ List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
Map<String, Set<String>> toBoth = new HashMap<>();
Map<String, Set<String>> toOne = new HashMap<>();
+ Map<String, Set<String>> toOther = new HashMap<>();
toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+ toOther.put("libC", new HashSet<String>(Arrays.asList("0")));
fileNameToLibraryToContentIdMap.put("/file0", toBoth);
fileNameToLibraryToContentIdMap.put("/file1", toOne);
+ fileNameToLibraryToContentIdMap.put("/file2", toOther);
contentIdToFileContentMap.put("0", "license content #0");
contentIdToFileContentMap.put("1", "license content #1");
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
new PrintWriter(output), "");
assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING);
}
@Test
- public void testGenerateHtmlWithCustomHeading() {
+ public void testGenerateHtmlWithCustomHeading() throws Exception {
+ List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
Map<String, Set<String>> toBoth = new HashMap<>();
Map<String, Set<String>> toOne = new HashMap<>();
toBoth.put("", new HashSet<String>(Arrays.asList("0", "1")));
- toOne.put("", new HashSet<String>(Arrays.asList("0")));
+ toOne.put("", new HashSet<String>(Arrays.asList("0", "1")));
fileNameToLibraryToContentIdMap.put("/file0", toBoth);
fileNameToLibraryToContentIdMap.put("/file1", toOne);
@@ -258,30 +272,34 @@
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
new PrintWriter(output), HTML_CUSTOM_HEADING);
assertThat(output.toString()).isEqualTo(EXPECTED_OLD_HTML_STRING_WITH_CUSTOM_HEADING);
}
@Test
- public void testGenerateNewHtmlWithCustomHeading() {
+ public void testGenerateNewHtmlWithCustomHeading() throws Exception {
+ List<File> xmlFiles = new ArrayList<>();
Map<String, Map<String, Set<String>>> fileNameToLibraryToContentIdMap = new HashMap<>();
Map<String, String> contentIdToFileContentMap = new HashMap<>();
Map<String, Set<String>> toBoth = new HashMap<>();
Map<String, Set<String>> toOne = new HashMap<>();
+ Map<String, Set<String>> toOther = new HashMap<>();
toBoth.put("libA", new HashSet<String>(Arrays.asList("0")));
toBoth.put("libB", new HashSet<String>(Arrays.asList("1")));
toOne.put("libA", new HashSet<String>(Arrays.asList("0")));
+ toOther.put("libC", new HashSet<String>(Arrays.asList("0")));
fileNameToLibraryToContentIdMap.put("/file0", toBoth);
fileNameToLibraryToContentIdMap.put("/file1", toOne);
+ fileNameToLibraryToContentIdMap.put("/file2", toOther);
contentIdToFileContentMap.put("0", "license content #0");
contentIdToFileContentMap.put("1", "license content #1");
StringWriter output = new StringWriter();
LicenseHtmlGeneratorFromXml.generateHtml(
- fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
+ xmlFiles, fileNameToLibraryToContentIdMap, contentIdToFileContentMap,
new PrintWriter(output), HTML_CUSTOM_HEADING);
assertThat(output.toString()).isEqualTo(EXPECTED_NEW_HTML_STRING_WITH_CUSTOM_HEADING);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
index 625b214..d78f8e7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
@@ -63,6 +63,7 @@
final Button button = mPreference.getButton();
assertThat(button.getText().toString()).isEqualTo(testTitle);
+ assertThat(mPreference.getTitle().toString()).isEqualTo(testTitle);
}
@Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 133b52d..42b992f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -211,6 +211,7 @@
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
Settings.Secure.WEAR_TALKBACK_ENABLED,
- Settings.Secure.HBM_SETTING_KEY
+ Settings.Secure.HBM_SETTING_KEY,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 796b4c4..f0915f8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -337,8 +337,15 @@
VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(
- Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT,
- NON_NEGATIVE_INTEGER_VALIDATOR);
+ Global.Wearable.EARLY_UPDATES_STATUS,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_NOT_STARTED),
+ String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_STARTED),
+ String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SUCCESS),
+ String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED),
+ String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED),
+ }));
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index bb01f4f..14b5855 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -345,5 +345,6 @@
VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 4e2bce2..c3b645e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1825,6 +1825,9 @@
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
+ SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
p.end(accessibilityToken);
final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 760f147..ba7a9bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,6 +98,7 @@
Settings.System.VOLUME_VOICE, // deprecated since API 2?
Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+ Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
Settings.System.SCREEN_BRIGHTNESS_FLOAT,
@@ -656,7 +657,7 @@
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
- Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MAX_RESET_COUNT);
+ Settings.Global.Wearable.EARLY_UPDATES_STATUS);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 154a6fc..26feaf9 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -137,6 +137,22 @@
]
}
],
+ "ironwood-postsubmit": [
+ {
+ "name": "PlatformScenarioTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.platform.test.scenario.sysui"
+ }
+ ]
+ }
+ ],
"auto-end-to-end-postsubmit": [
{
"name": "AndroidAutomotiveHomeTests",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
new file mode 100644
index 0000000..925fae0e
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class BindServiceViaContextDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("bindService", "bindServiceAsUser", "unbindService")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Binding or unbinding services are synchronous calls, please make " +
+ "sure you're on a @Background Executor."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "BindServiceViaContextDetector",
+ briefDescription = "Service bound/unbound via Context, please make sure " +
+ "you're on a background thread.",
+ explanation =
+ "Binding or unbinding services are synchronous calls to ActivityManager, " +
+ "they usually take multiple milliseconds to complete and will make" +
+ "the caller drop frames. Make sure you're on a @Background Executor.",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(BindServiceViaContextDetector::class.java, Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 397a110..226aebbd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,7 +27,8 @@
class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
- get() = listOf(BroadcastSentViaContextDetector.ISSUE)
+ get() = listOf(BindServiceViaContextDetector.ISSUE,
+ BroadcastSentViaContextDetector.ISSUE)
override val api: Int
get() = CURRENT_API
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
new file mode 100644
index 0000000..bf685f7
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/BindServiceViaContextDetectorTest.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class BindServiceViaContextDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = BindServiceViaContextDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(
+ BindServiceViaContextDetector.ISSUE)
+
+ private val explanation = "Binding or unbinding services are synchronous calls"
+
+ @Test
+ fun testBindService() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+
+ public class TestClass1 {
+ public void bind(Context context) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ context.bindService(intent, null, 0);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testBindServiceAsUser() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserHandle;
+
+ public class TestClass1 {
+ public void bind(Context context) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testUnbindService() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ public class TestClass1 {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(BindServiceViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ import android.os.UserHandle;
+
+ public class Context {
+ public void bindService(Intent intent) {};
+ public void bindServiceAsUser(Intent intent, ServiceConnection connection, int flags,
+ UserHandle userHandle) {};
+ public void unbindService(ServiceConnection connection) {};
+ }
+ """
+ )
+
+ private val serviceConnectionStub: TestFile = java(
+ """
+ package android.content;
+
+ public class ServiceConnection {}
+ """
+ )
+
+ private val userHandleStub: TestFile = java(
+ """
+ package android.os;
+
+ public enum UserHandle {
+ ALL
+ }
+ """
+ )
+
+ private val stubs = arrayOf(contextStub, serviceConnectionStub, userHandleStub)
+}
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index a96e5339..38d636d7 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -6,16 +6,16 @@
## Adding a new Quick Affordance
### Step 1: create a new quick affordance config
-* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
* The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
* It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
* When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
-* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/domain/quickaffordance)
### Step 2: choose a position and priority
-* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceRegistry](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt)
* Place the new class in one of the available positions in the `configsByPosition` property, note:
* In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
* Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 01e3de2..898935f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -35,7 +35,7 @@
<!-- need to keep this outer view in order to have a correctly sized anchor
for the dropdown menu, as well as dropdown background in the right place -->
- <LinearLayout
+ <com.android.keyguard.KeyguardUserSwitcherAnchor
android:id="@+id/user_switcher_anchor"
android:orientation="horizontal"
android:layout_height="wrap_content"
@@ -48,7 +48,7 @@
android:textDirection="locale"
android:layout_width="@dimen/bouncer_user_switcher_width"
android:layout_height="wrap_content" />
- </LinearLayout>>
+ </com.android.keyguard.KeyguardUserSwitcherAnchor>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 70a7709..c972624 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -35,10 +35,20 @@
app:layout_constraintBottom_toBottomOf="parent" />
<LinearLayout
+ android:id="@+id/dream_overlay_extra_items"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin"
+ app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" />
+
+ <LinearLayout
android:id="@+id/dream_overlay_system_status"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
+ android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin"
app:layout_constraintEnd_toEndOf="parent">
<com.android.systemui.statusbar.AlphaOptimizedImageView
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 60932a7..786b6b8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1450,6 +1450,7 @@
<dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
<dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
<dimen name="dream_overlay_grey_chip_width">56dp</dimen>
+ <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen>
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9c2542c..7f3caec 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -799,7 +799,7 @@
<!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] -->
<string name="keyguard_unlock">Swipe up to open</string>
- <!-- Message shown when lock screen is unlocked (ie: by trust agent) and the user taps the empty space on the lock screen and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+ <!-- Message shown when lock screen is unlocked (ie: by trust agent or face auth). Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
<string name="keyguard_unlock_press">Press the unlock icon to open</string>
<!-- Message shown when non-bypass face authentication succeeds. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
@@ -813,6 +813,10 @@
<!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
<string name="keyguard_face_successful_unlock_press_alt_3">Face recognized. Press the unlock icon to open.</string>
+ <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+ <string name="keyguard_face_successful_unlock">Unlocked by face</string>
+ <!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
+ <string name="keyguard_face_successful_unlock_alt1">Face recognized</string>
<!-- Messages shown when users press outside of udfps region during -->
<string-array name="udfps_accessibility_touch_hints">
@@ -887,7 +891,8 @@
<!-- Accessibility label for the button that opens the user switcher. -->
<string name="accessibility_multi_user_switch_switcher">Switch user</string>
- <!-- Accessibility label for the button that opens the user switcher and announces the current user. -->
+ <!-- Accessibility role description for the element that opens the user switcher list. -->
+ <string name="accessibility_multi_user_list_switcher">pulldown menu</string>
<!-- Accessibility label for the user icon on the lock screen. -->
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index ee0c4fb6..a82684d03 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -43,7 +43,7 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
@@ -61,7 +61,7 @@
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
- app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
@@ -76,7 +76,7 @@
app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
@@ -100,8 +100,8 @@
android:layout_height="0dp"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 203b236..7e42e1b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -170,7 +170,7 @@
/** @return {@link SurfaceControl.Transaction} instance with vsync-id */
public static SurfaceControl.Transaction newSurfaceControlTransaction() {
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
return tx;
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 0149751..4613e8b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -100,4 +100,14 @@
* Sent when the desired dark intensity of the nav buttons has changed
*/
void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
+
+ /**
+ * Sent when screen started turning on.
+ */
+ void onScreenTurningOn() = 23;
+
+ /**
+ * Sent when screen started turning off.
+ */
+ void onScreenTurningOff() = 24;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 9265f07..33e8e35 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -122,12 +122,13 @@
IRemoteTransitionFinishedCallback finishCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
final RemoteAnimationTargetCompat[] appsCompat =
- RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
final RemoteAnimationTargetCompat[] wallpapersCompat =
- RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
- // TODO(bc-unlock): Build wrapped object for non-apps target.
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
final RemoteAnimationTargetCompat[] nonAppsCompat =
- new RemoteAnimationTargetCompat[0];
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, false /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
boolean isReturnToHome = false;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index ef9e095..7c3b5fc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -16,7 +16,9 @@
package com.android.systemui.shared.system;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -24,6 +26,8 @@
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -76,7 +80,7 @@
private final SurfaceControl mStartLeash;
- // Fields used only to unrap into RemoteAnimationTarget
+ // Fields used only to unwrap into RemoteAnimationTarget
private final Rect startBounds;
public final boolean willShowImeOnTarget;
@@ -203,8 +207,19 @@
public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order,
TransitionInfo info, SurfaceControl.Transaction t) {
- taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
mode = newModeToLegacyMode(change.getMode());
+ taskInfo = change.getTaskInfo();
+ if (taskInfo != null) {
+ taskId = taskInfo.taskId;
+ isNotInRecents = !taskInfo.isRunning;
+ activityType = taskInfo.getActivityType();
+ windowConfiguration = taskInfo.configuration.windowConfiguration;
+ } else {
+ taskId = INVALID_TASK_ID;
+ isNotInRecents = true;
+ activityType = ACTIVITY_TYPE_UNDEFINED;
+ windowConfiguration = new WindowConfiguration();
+ }
// TODO: once we can properly sync transactions across process, then get rid of this leash.
leash = createLeash(info, change, order, t);
@@ -221,22 +236,12 @@
prefixOrderIndex = order;
// TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
contentInsets = new Rect(0, 0, 0, 0);
- if (change.getTaskInfo() != null) {
- isNotInRecents = !change.getTaskInfo().isRunning;
- activityType = change.getTaskInfo().getActivityType();
- } else {
- isNotInRecents = true;
- activityType = ACTIVITY_TYPE_UNDEFINED;
- }
- taskInfo = change.getTaskInfo();
allowEnterPip = change.getAllowEnterPip();
mStartLeash = null;
rotationChange = change.getEndRotation() - change.getStartRotation();
- windowType = INVALID_WINDOW_TYPE;
+ windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
+ ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE;
- windowConfiguration = change.getTaskInfo() != null
- ? change.getTaskInfo().configuration.windowConfiguration
- : new WindowConfiguration();
startBounds = change.getStartAbsBounds();
willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
}
@@ -251,37 +256,62 @@
}
/**
- * Represents a TransitionInfo object as an array of old-style targets
+ * Represents a TransitionInfo object as an array of old-style app targets
+ *
+ * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
+ * populated by this function. If null, it is ignored.
+ */
+ public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info,
+ SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
+ final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null) continue;
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // Children always come before parent since changes are in top-to-bottom z-order.
+ if (taskInfo != null) {
+ if (childTaskTargets.contains(taskInfo.taskId)) {
+ // has children, so not a leaf. Skip.
+ continue;
+ }
+ if (taskInfo.hasParentTask()) {
+ childTaskTargets.put(taskInfo.parentTaskId, change);
+ }
+ }
+
+ final RemoteAnimationTargetCompat targetCompat =
+ new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
+ if (leashMap != null) {
+ leashMap.put(change.getLeash(), targetCompat.leash);
+ }
+ out.add(targetCompat);
+ }
+
+ return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
+ }
+
+ /**
+ * Represents a TransitionInfo object as an array of old-style non-app targets
*
* @param wallpapers If true, this will return wallpaper targets; otherwise it returns
* non-wallpaper targets.
* @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
* populated by this function. If null, it is ignored.
*/
- public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers,
+ public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
- final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
+
for (int i = 0; i < info.getChanges().size(); i++) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() != null) continue;
+
final boolean changeIsWallpaper =
(change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
if (wallpapers != changeIsWallpaper) continue;
- if (!wallpapers) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- // Children always come before parent since changes are in top-to-bottom z-order.
- if (taskInfo != null) {
- if (childTaskTargets.contains(taskInfo.taskId)) {
- // has children, so not a leaf. Skip.
- continue;
- }
- if (taskInfo.hasParentTask()) {
- childTaskTargets.put(taskInfo.parentTaskId, change);
- }
- }
- }
-
final RemoteAnimationTargetCompat targetCompat =
new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
if (leashMap != null) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 7c1ef8c..f679225 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -128,9 +128,10 @@
IRemoteTransitionFinishedCallback finishedCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
final RemoteAnimationTargetCompat[] apps =
- RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
final RemoteAnimationTargetCompat[] wallpapers =
- RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
mToken = transition;
// This transition is for opening recents, so recents is on-top. We want to draw
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index f697e25..3517d22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1129,11 +1129,13 @@
Log.e(TAG, "Current user in user switcher is null.");
return;
}
+ final String currentUserName = mUserSwitcherController.getCurrentUserName();
Drawable userIcon = findUserIcon(currentUser.info.id);
((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon);
- mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName());
+ mUserSwitcher.setText(currentUserName);
- ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
+ KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+
BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -1213,7 +1215,6 @@
anchor.setOnClickListener((v) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
-
mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
mPopup.setAnchorView(anchor);
mPopup.setAdapter(adapter);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index ecd88e6..2a2e9bf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -142,7 +142,9 @@
protected void verifyPasswordAndUnlock() {
String entry = mPasswordEntry.getText();
- if (entry.length() < 4) {
+ // A SIM PIN is 4 to 8 decimal digits according to
+ // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
+ if ((entry.length() < 4) || (entry.length() > 8)) {
// otherwise, display a message to the user, and don't submit.
mMessageAreaController.setMessage(
com.android.systemui.R.string.kg_invalid_sim_pin_hint);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 89d6fb5..acbea1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -29,7 +29,7 @@
/**
* Translates items away/towards the hinge when the device is opened/closed. This is controlled by
- * the set of ids, which also dictact which direction to move and when, via a filter function.
+ * the set of ids, which also dictate which direction to move and when, via a filter function.
*/
@SysUIUnfoldScope
class KeyguardUnfoldTransition
@@ -55,7 +55,9 @@
ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever),
ViewIdToTranslate(
R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly),
- ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)),
+ ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever),
+ ViewIdToTranslate(R.id.start_button, LEFT, filterNever),
+ ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)),
progressProvider = unfoldProgressProvider)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 99e0ce2..7a42803 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -271,7 +271,7 @@
* like fingerprint authentication errors.
*
* @param message Message that indicates an error.
- * @see KeyguardIndicationController.BaseKeyguardCallback#HIDE_DELAY_MS
+ * @see KeyguardIndicationController#DEFAULT_HIDE_DELAY_MS
* @see KeyguardIndicationController#showTransientIndication(CharSequence)
*/
public void onTrustAgentErrorMessage(CharSequence message) { }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
new file mode 100644
index 0000000..5f3ba72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.keyguard
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.LinearLayout
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.systemui.R
+
+/**
+ * Custom View for the multi-user switcher pull-down menu anchor
+ */
+class KeyguardUserSwitcherAnchor @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+
+ override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo {
+ val info = super.createAccessibilityNodeInfo()
+ AccessibilityNodeInfoCompat.wrap(info).roleDescription =
+ context.getString(R.string.accessibility_multi_user_list_switcher)
+ return info
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 5c84ff3..614a87f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -87,7 +87,6 @@
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -102,7 +101,6 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -313,7 +311,6 @@
@Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
- @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
@Inject Lazy<NotificationGroupManagerLegacy> mNotificationGroupManager;
@Inject Lazy<VisualStabilityManager> mVisualStabilityManager;
@Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@@ -322,7 +319,6 @@
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@Inject Lazy<NotificationLogger> mNotificationLogger;
- @Inject Lazy<NotificationFilter> mNotificationFilter;
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@@ -529,8 +525,6 @@
mNotificationLockscreenUserManager::get);
mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get);
mProviders.put(NotificationGroupManagerLegacy.class, mNotificationGroupManager::get);
- mProviders.put(NotificationGroupAlertTransferHelper.class,
- mNotificationGroupAlertTransferHelper::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
mProviders.put(NotificationRemoteInputManager.class,
@@ -538,7 +532,6 @@
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
mProviders.put(NotificationListener.class, mNotificationListener::get);
mProviders.put(NotificationLogger.class, mNotificationLogger::get);
- mProviders.put(NotificationFilter.class, mNotificationFilter::get);
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index 835025b..e82d0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -16,8 +16,6 @@
package com.android.systemui.charging;
-import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -32,13 +30,14 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.ripple.RippleShader.RippleShape;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
* @hide
*/
public class WirelessChargingAnimation {
-
+ public static final int UNKNOWN_BATTERY_LEVEL = -1;
public static final long DURATION = 1500;
private static final String TAG = "WirelessChargingView";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -58,11 +57,12 @@
* before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
* @hide
*/
- public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
+ private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
- UiEventLogger uiEventLogger) {
+ RippleShape rippleShape, UiEventLogger uiEventLogger) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
- transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger);
+ transmittingBatteryLevel, batteryLevel, callback, isDozing,
+ rippleShape, uiEventLogger);
}
/**
@@ -72,9 +72,10 @@
*/
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
@Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
- Callback callback, boolean isDozing, UiEventLogger uiEventLogger) {
+ Callback callback, boolean isDozing, RippleShape rippleShape,
+ UiEventLogger uiEventLogger) {
return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
- batteryLevel, callback, isDozing, uiEventLogger);
+ batteryLevel, callback, isDozing, rippleShape, uiEventLogger);
}
/**
@@ -82,9 +83,10 @@
* battery level without charging number shown.
*/
public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel(
- @NonNull Context context, UiEventLogger uiEventLogger) {
+ @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) {
return makeWirelessChargingAnimation(context, null,
- UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, uiEventLogger);
+ UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false,
+ rippleShape, uiEventLogger);
}
/**
@@ -121,10 +123,10 @@
public WirelessChargingView(Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback,
- boolean isDozing, UiEventLogger uiEventLogger) {
+ boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) {
mCallback = callback;
mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
- isDozing);
+ isDozing, rippleShape);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
mUiEventLogger = uiEventLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 65400c2..47ea27f 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,7 +33,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleShader;
+import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -41,37 +41,36 @@
/**
* @hide
*/
-public class WirelessChargingLayout extends FrameLayout {
- public static final int UNKNOWN_BATTERY_LEVEL = -1;
+final class WirelessChargingLayout extends FrameLayout {
private static final long RIPPLE_ANIMATION_DURATION = 1500;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
private RippleView mRippleView;
- public WirelessChargingLayout(Context context) {
+ WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
+ boolean isDozing, RippleShape rippleShape) {
super(context);
- init(context, null, false);
+ init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape);
}
- public WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel,
- boolean isDozing) {
+ private WirelessChargingLayout(Context context) {
super(context);
- init(context, null, transmittingBatteryLevel, batteryLevel, isDozing);
+ init(context, null, /* isDozing= */ false, RippleShape.CIRCLE);
}
- public WirelessChargingLayout(Context context, AttributeSet attrs) {
+ private WirelessChargingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- init(context, attrs, false);
+ init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE);
}
- private void init(Context c, AttributeSet attrs, boolean isDozing) {
- init(c, attrs, -1, -1, false);
+ private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) {
+ init(c, attrs, -1, -1, isDozing, rippleShape);
}
private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel,
- int batteryLevel, boolean isDozing) {
+ int batteryLevel, boolean isDozing, RippleShape rippleShape) {
final boolean showTransmittingBatteryLevel =
- (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL);
+ (transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL);
// set style based on background
int style = R.style.ChargingAnim_WallpaperBackground;
@@ -84,7 +83,7 @@
// amount of battery:
final TextView percentage = findViewById(R.id.wireless_charging_percentage);
- if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
+ if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) {
percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f));
percentage.setAlpha(0);
}
@@ -138,8 +137,7 @@
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
- // TODO: Make rounded box shape if the device is tablet.
- mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
+ mRippleView.setupShader(rippleShape);
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
@@ -233,8 +231,12 @@
int width = getMeasuredWidth();
int height = getMeasuredHeight();
mRippleView.setCenter(width * 0.5f, height * 0.5f);
- float maxSize = Math.max(width, height);
- mRippleView.setMaxSize(maxSize, maxSize);
+ if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+ mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+ } else {
+ float maxSize = Math.max(width, height);
+ mRippleView.setMaxSize(maxSize, maxSize);
+ }
mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
android.R.attr.colorAccent).getDefaultColor());
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 78a45f9..b6923a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -75,25 +75,12 @@
* Initializes all the WMShell components before starting any of the SystemUI components.
*/
default void init() {
- // TODO(238217847): To be removed once the dependencies are inverted and ShellController can
- // inject these classes directly, otherwise, it's currently needed to ensure that these
- // classes are created and set on the controller before onInit() is called
- getShellInit();
- getShellCommandHandler();
getShell().onInit();
}
@WMSingleton
ShellInterface getShell();
- // TODO(238217847): To be removed once ShellController can inject ShellInit directly
- @WMSingleton
- ShellInit getShellInit();
-
- // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly
- @WMSingleton
- ShellCommandHandler getShellCommandHandler();
-
@WMSingleton
Optional<OneHanded> getOneHanded();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java
new file mode 100644
index 0000000..193d6f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.dreams;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamOverlayStatusBarItemsProvider} provides extra dream overlay status bar items. A
+ * callback can be registered that will be informed of items being added or removed from the
+ * provider.
+ */
+@SysUISingleton
+public class DreamOverlayStatusBarItemsProvider implements
+ CallbackController<DreamOverlayStatusBarItemsProvider.Callback> {
+ /**
+ * Represents one item in the dream overlay status bar.
+ */
+ public interface StatusBarItem {
+ /**
+ * Return the {@link View} associated with this item.
+ */
+ View getView();
+ }
+
+ /**
+ * A callback to be registered with the provider to be informed of when the list of status bar
+ * items has changed.
+ */
+ public interface Callback {
+ /**
+ * Inform the callback that status bar items have changed.
+ */
+ void onStatusBarItemsChanged(List<StatusBarItem> newItems);
+ }
+
+ private final Executor mExecutor;
+ private final List<StatusBarItem> mItems = new ArrayList<>();
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
+ @Inject
+ public DreamOverlayStatusBarItemsProvider(@Main Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void addCallback(@NonNull Callback callback) {
+ mExecutor.execute(() -> {
+ Objects.requireNonNull(callback, "Callback must not be null.");
+ if (mCallbacks.contains(callback)) {
+ return;
+ }
+
+ mCallbacks.add(callback);
+ if (!mItems.isEmpty()) {
+ callback.onStatusBarItemsChanged(mItems);
+ }
+ });
+ }
+
+ @Override
+ public void removeCallback(@NonNull Callback callback) {
+ mExecutor.execute(() -> {
+ Objects.requireNonNull(callback, "Callback must not be null.");
+ mCallbacks.remove(callback);
+ });
+ }
+
+ /**
+ * Adds an item to the dream overlay status bar.
+ */
+ public void addStatusBarItem(StatusBarItem item) {
+ mExecutor.execute(() -> {
+ if (!mItems.contains(item)) {
+ mItems.add(item);
+ mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems));
+ }
+ });
+ }
+
+ /**
+ * Removes an item from the dream overlay status bar.
+ */
+ public void removeStatusBarItem(StatusBarItem item) {
+ mExecutor.execute(() -> {
+ if (mItems.remove(item)) {
+ mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems));
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index a25257d..7e4a108 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -29,6 +30,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -58,6 +60,7 @@
public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
+ private ViewGroup mSystemStatusViewGroup;
public DreamOverlayStatusBarView(Context context) {
this(context, null);
@@ -94,6 +97,8 @@
fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));
mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,
fetchStatusIconForResId(R.id.dream_overlay_priority_mode));
+
+ mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
}
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
@@ -107,6 +112,11 @@
icon.setVisibility(show ? View.VISIBLE : View.GONE);
}
+ void setExtraStatusBarItemViews(List<View> views) {
+ mSystemStatusViewGroup.removeAllViews();
+ views.forEach(view -> mSystemStatusViewGroup.addView(view));
+ }
+
private View fetchStatusIconForResId(int resId) {
final View statusIcon = findViewById(resId);
return Objects.requireNonNull(statusIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 55c1806..65cfae1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -38,6 +38,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -47,10 +48,13 @@
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.DateFormatUtil;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -69,7 +73,10 @@
private final Optional<DreamOverlayNotificationCountProvider>
mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
+ private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
private final Executor mMainExecutor;
+ private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
+ new ArrayList<>();
private boolean mIsAttached;
@@ -116,6 +123,9 @@
? buildNotificationsContentDescription(notificationCount)
: null);
+ private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
+ this::onStatusBarItemsChanged;
+
@Inject
public DreamOverlayStatusBarViewController(
DreamOverlayStatusBarView view,
@@ -129,7 +139,8 @@
IndividualSensorPrivacyController sensorPrivacyController,
Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
ZenModeController zenModeController,
- StatusBarWindowStateController statusBarWindowStateController) {
+ StatusBarWindowStateController statusBarWindowStateController,
+ DreamOverlayStatusBarItemsProvider statusBarItemsProvider) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -140,6 +151,7 @@
mDateFormatUtil = dateFormatUtil;
mSensorPrivacyController = sensorPrivacyController;
mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider;
+ mStatusBarItemsProvider = statusBarItemsProvider;
mZenModeController = zenModeController;
// Register to receive show/hide updates for the system status bar. Our custom status bar
@@ -166,6 +178,8 @@
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.addCallback(mNotificationCountCallback));
+ mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
+
mTouchInsetSession.addViewToTracking(mView);
}
@@ -177,6 +191,7 @@
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mDreamOverlayNotificationCountProvider.ifPresent(
provider -> provider.removeCallback(mNotificationCountCallback));
+ mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);
mTouchInsetSession.clear();
mIsAttached = false;
@@ -271,4 +286,16 @@
}
});
}
+
+ private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
+ mMainExecutor.execute(() -> {
+ mExtraStatusBarItems.clear();
+ mExtraStatusBarItems.addAll(newItems);
+ mView.setExtraStatusBarItemViews(
+ newItems
+ .stream()
+ .map(StatusBarItem::getView)
+ .collect(Collectors.toList()));
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index e4fc88b..6eb77bd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -188,6 +188,7 @@
new ReleasedFlag(1000);
public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
+ public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false);
// 1100 - windowing
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index bd00ce6..1f52fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -27,6 +27,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,8 +57,11 @@
public class KeyguardIndicationRotateTextViewController extends
ViewController<KeyguardIndicationTextView> implements Dumpable {
public static String TAG = "KgIndicationRotatingCtrl";
- private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds
- public static final long IMPORTANT_MSG_MIN_DURATION = 2000L + 600L; // 2000ms + [Y in duration]
+ private static final long DEFAULT_INDICATION_SHOW_LENGTH =
+ KeyguardIndicationController.DEFAULT_HIDE_DELAY_MS
+ - KeyguardIndicationTextView.Y_IN_DURATION;
+ public static final long IMPORTANT_MSG_MIN_DURATION =
+ 2000L + KeyguardIndicationTextView.Y_IN_DURATION;
private final StatusBarStateController mStatusBarStateController;
private final float mMaxAlpha;
@@ -375,6 +379,7 @@
public static final int INDICATION_TYPE_USER_LOCKED = 8;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
+ public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12;
@IntDef({
INDICATION_TYPE_NONE,
@@ -388,7 +393,8 @@
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
- INDICATION_TYPE_BIOMETRIC_MESSAGE
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndicationType{}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 044a57c..0a55294 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -41,4 +41,12 @@
override fun onScreenTurnedOn() {
listeners.forEach(ScreenListener::onScreenTurnedOn)
}
+
+ override fun onScreenTurningOff() {
+ listeners.forEach(ScreenListener::onScreenTurningOff)
+ }
+
+ override fun onScreenTurningOn(ignored: Runnable) {
+ listeners.forEach(ScreenListener::onScreenTurningOn)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4ff008f..430b59c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -70,6 +71,7 @@
KeyguardUserSwitcherComponent.class},
includes = {
FalsingModule.class,
+ KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
KeyguardUseCaseModule.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 43c4fa0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.systemui.keyguard.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
-interface KeyguardQuickAffordanceRepository {
- fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
-}
-
-/** Real implementation of [KeyguardQuickAffordanceRepository] */
-@SysUISingleton
-class KeyguardQuickAffordanceRepositoryImpl
-@Inject
-constructor(
- private val configs: KeyguardQuickAffordanceConfigs,
-) : KeyguardQuickAffordanceRepository {
-
- /** Returns an observable for the quick affordance model in the given position. */
- override fun affordance(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- val configs = configs.getAll(position)
- return combine(configs.map { config -> config.state }) { states ->
- val index = states.indexOfFirst { state -> state is State.Visible }
- val visibleState =
- if (index != -1) {
- states[index] as State.Visible
- } else {
- null
- }
- if (visibleState != null) {
- KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index]::class,
- icon = visibleState.icon,
- contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
- )
- } else {
- KeyguardQuickAffordanceModel.Hidden
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 1a5670c..d15d7f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -16,22 +16,10 @@
package com.android.systemui.keyguard.data.repository
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
import dagger.Binds
import dagger.Module
@Module
interface KeyguardRepositoryModule {
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
-
- @Binds
- fun keyguardQuickAffordanceRepository(
- impl: KeyguardQuickAffordanceRepositoryImpl
- ): KeyguardQuickAffordanceRepository
-
- @Binds
- fun keyguardQuickAffordanceConfigs(
- impl: KeyguardQuickAffordanceConfigsImpl
- ): KeyguardQuickAffordanceConfigs
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 09785df..411a2ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -15,11 +15,11 @@
*
*/
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
import androidx.annotation.StringRes
import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
/**
@@ -27,7 +27,6 @@
* lock-screen).
*/
sealed class KeyguardQuickAffordanceModel {
-
/** No affordance should show up. */
object Hidden : KeyguardQuickAffordanceModel()
@@ -43,4 +42,21 @@
*/
@StringRes val contentDescriptionResourceId: Int,
) : KeyguardQuickAffordanceModel()
+
+ companion object {
+ fun from(
+ state: KeyguardQuickAffordanceConfig.State?,
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ ): KeyguardQuickAffordanceModel {
+ return when (state) {
+ is KeyguardQuickAffordanceConfig.State.Visible ->
+ Visible(
+ configKey = configKey,
+ icon = state.icon,
+ contentDescriptionResourceId = state.contentDescriptionResourceId,
+ )
+ else -> Hidden
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
index b71e15d..581dafa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.keyguard.domain.model
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 3202ecb..8f32ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Context
import android.content.Intent
@@ -36,6 +36,7 @@
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
/** Home controls quick affordance data source. */
@@ -50,7 +51,13 @@
private val appContext = context.applicationContext
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
- stateInternal(component.getControlsListingController().getOrNull())
+ component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
+ if (canShowWhileLocked) {
+ stateInternal(component.getControlsListingController().getOrNull())
+ } else {
+ flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+ }
override fun onQuickAffordanceClicked(
animationController: ActivityLaunchAnimator.Controller?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 67a776e..8fb952c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Intent
import androidx.annotation.StringRes
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
new file mode 100644
index 0000000..a7b3828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.systemui.keyguard.domain.quickaffordance
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardQuickAffordanceModule {
+ @Binds
+ fun keyguardQuickAffordanceRegistry(
+ impl: KeyguardQuickAffordanceRegistryImpl
+ ): KeyguardQuickAffordanceRegistry
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 7164215..2c37f93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -15,29 +15,25 @@
*
*/
-package com.android.systemui.keyguard.data.config
+package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlin.reflect.KClass
-/** Injectable provider of the positioning of the known quick affordance configs. */
-interface KeyguardQuickAffordanceConfigs {
+/** Central registry of all known quick affordance configs. */
+interface KeyguardQuickAffordanceRegistry {
fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
}
-class KeyguardQuickAffordanceConfigsImpl
+class KeyguardQuickAffordanceRegistryImpl
@Inject
constructor(
homeControls: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceConfigs {
+) : KeyguardQuickAffordanceRegistry {
private val configsByPosition =
mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index ea6497e..c8e5e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index cc5a997..885af33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
index c44c2c9..403d343 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
@@ -26,4 +26,9 @@
fun launchQuickAffordance(
impl: LaunchKeyguardQuickAffordanceUseCaseImpl
): LaunchKeyguardQuickAffordanceUseCase
+
+ @Binds
+ fun observeKeyguardQuickAffordance(
+ impl: ObserveKeyguardQuickAffordanceUseCaseImpl
+ ): ObserveKeyguardQuickAffordanceUseCase
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
index eef8ec3..8dee8b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
@@ -16,26 +16,33 @@
package com.android.systemui.keyguard.domain.usecase
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-/** Use-case for observing the model of a quick affordance in the keyguard. */
-class ObserveKeyguardQuickAffordanceUseCase
+/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
+interface ObserveKeyguardQuickAffordanceUseCase {
+ operator fun invoke(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel>
+}
+
+class ObserveKeyguardQuickAffordanceUseCaseImpl
@Inject
constructor(
- private val repository: KeyguardQuickAffordanceRepository,
+ private val registry: KeyguardQuickAffordanceRegistry,
private val isDozingUseCase: ObserveIsDozingUseCase,
private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) {
- operator fun invoke(
+) : ObserveKeyguardQuickAffordanceUseCase {
+ override fun invoke(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return combine(
- repository.affordance(position),
+ affordance(position),
isDozingUseCase(),
isKeyguardShowingUseCase(),
) { affordance, isDozing, isKeyguardShowing ->
@@ -46,4 +53,23 @@
}
}
}
+
+ private fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = registry.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index =
+ states.indexOfFirst { state ->
+ state is KeyguardQuickAffordanceConfig.State.Visible
+ }
+ val visibleState =
+ if (index != -1) {
+ states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ } else {
+ null
+ }
+ KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
index f8db90f..9315339 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
@@ -17,9 +17,9 @@
package com.android.systemui.keyguard.domain.usecase
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import javax.inject.Inject
import kotlin.reflect.KClass
@@ -27,7 +27,7 @@
class OnKeyguardQuickAffordanceClickedUseCase
@Inject
constructor(
- private val configs: KeyguardQuickAffordanceConfigs,
+ private val registry: KeyguardQuickAffordanceRegistry,
private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
) {
operator fun invoke(
@@ -35,7 +35,7 @@
animationController: ActivityLaunchAnimator.Controller?,
) {
@Suppress("UNCHECKED_CAST")
- val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+ val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
when (val result = config.onQuickAffordanceClicked(animationController)) {
is OnClickedResult.StartActivity ->
launchAffordanceUseCase(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 4b69a81..d296e76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
@@ -24,8 +26,6 @@
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 81efdf5..e0c8d66 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -266,11 +266,11 @@
}
/**
- * Are there any media notifications active, including the recommendation?
+ * Are there any active media entries, including the recommendation?
*/
- fun hasActiveMediaOrRecommendation() =
- userEntries.any { it.value.active } ||
- (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+ fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } ||
+ (smartspaceMediaData.isActive &&
+ (smartspaceMediaData.isValid() || reactivatedKey != null))
/**
* Are there any media entries we should display?
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e9b6af4..e360d10 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -263,6 +263,7 @@
}
private void onGroupActionTriggered(boolean isChecked, MediaDevice device) {
+ disableSeekBar();
if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
mController.addDeviceToPlayMedia(device);
} else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index e6116c1..43b0287 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -273,6 +273,8 @@
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
if (!mController.isVolumeControlEnabled(device)) {
disableSeekBar();
+ } else {
+ enableSeekBar();
}
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
@@ -417,11 +419,16 @@
return drawable;
}
- private void disableSeekBar() {
+ protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
}
+ private void enableSeekBar() {
+ mSeekBar.setEnabled(true);
+ mSeekBar.setOnTouchListener((v, event) -> false);
+ }
+
protected void setUpDeviceIcon(MediaDevice device) {
ThreadUtils.postOnBackgroundThread(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 5d7af52..6fe06e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -194,6 +194,11 @@
}
private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
+ if (device == null) {
+ return isSourceDevice
+ ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
+ : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return isSourceDevice
@@ -229,6 +234,9 @@
}
private int getInteractionDeviceType(MediaDevice device) {
+ if (device == null) {
+ return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
+ }
switch (device.getDeviceType()) {
case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 5f478ce..9ab83b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -56,7 +56,7 @@
internal val logger: MediaTttLogger,
internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
- @Main private val mainExecutor: DelayableExecutor,
+ @Main internal val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@@ -205,13 +205,15 @@
*
* @param appPackageName the package name of the app playing the media. Will be used to fetch
* the app icon and app name if overrides aren't provided.
+ *
+ * @return the content description of the icon.
*/
internal fun setIcon(
currentChipView: ViewGroup,
appPackageName: String?,
appIconDrawableOverride: Drawable? = null,
appNameOverride: CharSequence? = null,
- ) {
+ ): CharSequence {
val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon)
val iconInfo = getIconInfo(appPackageName)
@@ -224,6 +226,7 @@
appIconView.contentDescription = appNameOverride ?: iconInfo.iconName
appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon)
+ return appIconView.contentDescription.toString()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 3ea11b8..b94b8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,13 +122,12 @@
val chipState = newChipInfo.state
// App icon
- setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+ val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
// Text
val otherDeviceName = newChipInfo.routeInfo.name.toString()
- currentChipView.requireViewById<TextView>(R.id.text).apply {
- text = chipState.getChipTextString(context, otherDeviceName)
- }
+ val chipText = chipState.getChipTextString(context, otherDeviceName)
+ currentChipView.requireViewById<TextView>(R.id.text).text = chipText
// Loading
currentChipView.requireViewById<View>(R.id.loading).visibility =
@@ -145,17 +144,29 @@
// Failure
currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
chipState.isTransferFailure.visibleIfTrue()
+
+ // For accessibility
+ currentChipView.requireViewById<ViewGroup>(
+ R.id.media_ttt_sender_chip_inner
+ ).contentDescription = "$iconName $chipText"
}
override fun animateChipIn(chipView: ViewGroup) {
+ val chipInnerView = chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner)
ViewHierarchyAnimator.animateAddition(
- chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
+ chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = 500L,
+ duration = ANIMATION_DURATION,
includeMargins = true,
includeFadeIn = true,
)
+
+ // We can only request focus once the animation finishes.
+ mainExecutor.executeDelayed(
+ { chipInnerView.requestAccessibilityFocus() },
+ ANIMATION_DURATION
+ )
}
override fun removeChip(removalReason: String) {
@@ -186,3 +197,4 @@
}
const val SENDER_TAG = "MediaTapToTransferSender"
+private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index d701f33..c790cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs
import android.content.Intent
+import android.content.res.Configuration
import android.os.Handler
import android.os.UserManager
import android.provider.Settings
@@ -38,9 +39,11 @@
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.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -69,18 +72,43 @@
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
private val globalSetting: GlobalSettings,
- private val handler: Handler
+ private val handler: Handler,
+ private val configurationController: ConfigurationController,
) : ViewController<FooterActionsView>(view) {
private var globalActionsDialog: GlobalActionsDialogLite? = null
private var lastExpansion = -1f
private var listening: Boolean = false
+ private var inSplitShade = false
- private val alphaAnimator = TouchAnimator.Builder()
- .addFloat(mView, "alpha", 0f, 1f)
- .setStartDelay(0.9f)
+ private val singleShadeAnimator by lazy {
+ // In single shade, the actions footer should only appear at the end of the expansion,
+ // so that it doesn't overlap with the notifications panel.
+ TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
+ }
+
+ private val splitShadeAnimator by lazy {
+ // The Actions footer view has its own background which is the same color as the qs panel's
+ // background.
+ // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
+ // more opaque than the rest of the panel's background. Only applies to split shade.
+ val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
+ val bgAlphaAnimator =
+ TouchAnimator.Builder()
+ .addFloat(mView, "backgroundAlpha", 0f, 1f)
+ .setStartDelay(0.9f)
+ .build()
+ // In split shade, we want the actions footer to fade in exactly at the same time as the
+ // rest of the shade, as there is no overlap.
+ TouchAnimator.Builder()
+ .addFloat(alphaAnimator, "position", 0f, 1f)
+ .addFloat(bgAlphaAnimator, "position", 0f, 1f)
.build()
+ }
+
+ private val animators: TouchAnimator
+ get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
var visible = true
set(value) {
@@ -95,9 +123,7 @@
private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
@VisibleForTesting
- internal val securityFootersSeparator = View(context).apply {
- visibility = View.GONE
- }
+ internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -133,6 +159,17 @@
}
}
+ private val configurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
+ }
+
override fun onInit() {
multiUserSwitchController.init()
securityFooterController.init()
@@ -189,6 +226,9 @@
securityFooterController.setOnVisibilityChangedListener(visibilityListener)
fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
+ configurationController.addCallback(configurationListener)
+
+ updateResources()
updateView()
}
@@ -201,6 +241,7 @@
globalActionsDialog = null
setListening(false)
multiUserSetting.isListening = false
+ configurationController.removeCallback(configurationListener)
}
fun setListening(listening: Boolean) {
@@ -224,7 +265,7 @@
}
fun setExpansion(headerExpansionFraction: Float) {
- alphaAnimator.setPosition(headerExpansionFraction)
+ animators.setPosition(headerExpansionFraction)
}
fun setKeyguardShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 05038b7..309ac2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -27,6 +27,7 @@
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
+import androidx.annotation.Keep
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
@@ -45,6 +46,19 @@
private var qsDisabled = false
private var expansionAmount = 0f
+ /**
+ * Sets the alpha of the background of this view.
+ *
+ * Used from a [TouchAnimator] in the controller.
+ */
+ var backgroundAlpha: Float = 1f
+ @Keep
+ set(value) {
+ field = value
+ background?.alpha = (value * 255).toInt()
+ }
+ @Keep get
+
override fun onFinishInflate() {
super.onFinishInflate()
settingsContainer = findViewById(R.id.settings_button_container)
@@ -117,4 +131,4 @@
private const val TAG = "FooterActionsView"
private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
private val MotionEvent.string
- get() = "($id): ($x,$y)"
\ No newline at end of file
+ get() = "($id): ($x,$y)"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 2416808..7a98081 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -557,9 +557,9 @@
public void setQsExpansion(float expansion, float panelExpansionFraction,
float proposedTranslation, float squishinessFraction) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
- float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
+ float alphaProgress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
? mFullShadeProgress : panelExpansionFraction;
- setAlphaAnimationProgress(mInSplitShade ? progress : 1);
+ setAlphaAnimationProgress(mInSplitShade ? alphaProgress : 1);
mContainer.setExpansion(expansion);
final float translationScaleY = (mInSplitShade
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
@@ -600,7 +600,9 @@
}
mQSPanelController.setIsOnKeyguard(onKeyguard);
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
- mQSFooterActionController.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
+ float footerActionsExpansion =
+ onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
+ mQSFooterActionController.setExpansion(footerActionsExpansion);
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 324c019..7155626 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -128,6 +128,8 @@
if (mUsingMediaPlayer) {
mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mHorizontalLinearLayout.setVisibility(
+ mUsingHorizontalLayout ? View.VISIBLE : View.GONE);
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
@@ -445,6 +447,8 @@
mMediaHostView = hostView;
ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this;
ViewGroup currentParent = (ViewGroup) hostView.getParent();
+ Log.d(getDumpableTag(), "Reattaching media host: " + horizontal
+ + ", current " + currentParent + ", new " + newParent);
if (currentParent != newParent) {
if (currentParent != null) {
currentParent.removeView(hostView);
@@ -589,6 +593,7 @@
void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) {
if (horizontal != mUsingHorizontalLayout || force) {
+ Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force);
mUsingHorizontalLayout = horizontal;
ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
switchAllContentToParent(newParent, mTileLayout);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index ec61ea6..6d5f844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -88,6 +88,8 @@
public void onConfigurationChange(Configuration newConfig) {
mShouldUseSplitNotificationShade =
LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ mQSLogger.logOnConfigurationChanged(mLastOrientation, newConfig.orientation,
+ mView.getDumpableTag());
onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
mLastOrientation = newConfig.orientation;
@@ -164,6 +166,7 @@
mHost.addCallback(mQSHostCallback);
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
+ mQSLogger.logOnViewAttached(mLastOrientation, mView.getDumpableTag());
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -171,6 +174,7 @@
@Override
protected void onViewDetached() {
+ mQSLogger.logOnViewDetached(mLastOrientation, mView.getDumpableTag());
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.removeCallback(mQSHostCallback);
@@ -325,6 +329,8 @@
/* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
if (horizontal != mUsingHorizontalLayout || force) {
+ mQSLogger.logSwitchTileLayout(horizontal, mUsingHorizontalLayout, force,
+ mView.getDumpableTag());
mUsingHorizontalLayout = horizontal;
mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
updateMediaDisappearParameters();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4552abd..ac46c85 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,6 +29,7 @@
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger;
@@ -47,6 +48,7 @@
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -88,6 +90,10 @@
public static final int POSITION_AT_END = -1;
public static final String TILES_SETTING = Secure.QS_TILES;
+ // Shared prefs that hold tile lifecycle info.
+ @VisibleForTesting
+ static final String TILES = "tiles_prefs";
+
private final Context mContext;
private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>();
@@ -99,6 +105,7 @@
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
private final Executor mMainExecutor;
+ private final UserFileManager mUserFileManager;
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
@@ -110,6 +117,11 @@
private Context mUserContext;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
+ // Keep track of whether mTilesList contains the same information as the Settings value.
+ // This is a performance optimization to reduce the number of blocking calls to Settings from
+ // main thread.
+ // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
+ private boolean mTilesListDirty = true;
private final TileServiceRequestController mTileServiceRequestController;
private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
@@ -130,7 +142,8 @@
SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
- TileLifecycleManager.Factory tileLifecycleManagerFactory
+ TileLifecycleManager.Factory tileLifecycleManagerFactory,
+ UserFileManager userFileManager
) {
mIconController = iconController;
mContext = context;
@@ -143,6 +156,7 @@
mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
+ mUserFileManager = userFileManager;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mCentralSurfacesOptional = centralSurfacesOptional;
@@ -374,6 +388,7 @@
// the ones that are in the setting, update the Setting.
saveTilesToSettings(mTileSpecs);
}
+ mTilesListDirty = false;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
@@ -386,6 +401,11 @@
*/
@Override
public void removeTile(String spec) {
+ if (spec.startsWith(CustomTile.PREFIX)) {
+ // If the tile is removed (due to it not actually existing), mark it as removed. That
+ // way it will be marked as newly added if it appears in the future.
+ setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
+ }
mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
@@ -436,6 +456,7 @@
);
}
+ // When calling this, you may want to modify mTilesListDirty accordingly.
@MainThread
private void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
@@ -445,9 +466,15 @@
@MainThread
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
- final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
- final List<String> tileSpecs = loadTileSpecs(mContext, setting);
+ final List<String> tileSpecs;
+ if (!mTilesListDirty) {
+ tileSpecs = new ArrayList<>(mTileSpecs);
+ } else {
+ tileSpecs = loadTileSpecs(mContext,
+ mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser));
+ }
if (changeFunction.test(tileSpecs)) {
+ mTilesListDirty = true;
saveTilesToSettings(tileSpecs);
}
}
@@ -502,11 +529,12 @@
lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved();
mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
- TileLifecycleManager.setTileAdded(mContext, component, false);
+ setTileAdded(component, mCurrentUser, false);
lifecycleManager.flushMessagesAndUnbind();
}
}
if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
+ mTilesListDirty = true;
saveTilesToSettings(newTiles);
}
@@ -538,6 +566,36 @@
throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
}
+ /**
+ * Check if a particular {@link CustomTile} has been added for a user and has not been removed
+ * since.
+ * @param componentName the {@link ComponentName} of the
+ * {@link android.service.quicksettings.TileService} associated with the
+ * tile.
+ * @param userId the user to check
+ */
+ public boolean isTileAdded(ComponentName componentName, int userId) {
+ return mUserFileManager
+ .getSharedPreferences(TILES, 0, userId)
+ .getBoolean(componentName.flattenToString(), false);
+ }
+
+ /**
+ * Persists whether a particular {@link CustomTile} has been added and it's currently in the
+ * set of selected tiles ({@link #mTiles}.
+ * @param componentName the {@link ComponentName} of the
+ * {@link android.service.quicksettings.TileService} associated
+ * with the tile.
+ * @param userId the user for this tile
+ * @param added {@code true} if the tile is being added, {@code false} otherwise
+ */
+ public void setTileAdded(ComponentName componentName, int userId, boolean added) {
+ mUserFileManager.getSharedPreferences(TILES, 0, userId)
+ .edit()
+ .putBoolean(componentName.flattenToString(), added)
+ .apply();
+ }
+
protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index a49d3fd..3e445dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -127,6 +127,10 @@
TileLifecycleManager create(Intent intent, UserHandle userHandle);
}
+ public int getUserId() {
+ return mUser.getIdentifier();
+ }
+
public ComponentName getComponent() {
return mIntent.getComponent();
}
@@ -507,13 +511,4 @@
public interface TileChangeListener {
void onTileChanged(ComponentName tile);
}
-
- public static boolean isTileAdded(Context context, ComponentName component) {
- return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false);
- }
-
- public static void setTileAdded(Context context, ComponentName component, boolean added) {
- context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(),
- added).commit();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfc57db..e86bd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -109,9 +109,9 @@
void startLifecycleManagerAndAddTile() {
mStarted = true;
ComponentName component = mStateManager.getComponent();
- Context context = mServices.getContext();
- if (!TileLifecycleManager.isTileAdded(context, component)) {
- TileLifecycleManager.setTileAdded(context, component, true);
+ final int userId = mStateManager.getUserId();
+ if (!mServices.getHost().isTileAdded(component, userId)) {
+ mServices.getHost().setTileAdded(component, userId, true);
mStateManager.onTileAdded();
mStateManager.flushMessagesAndUnbind();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 86ef858..948fb14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -69,36 +69,63 @@
})
}
- fun logTileClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
+ int1 = eventId
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
}, {
- "[$str1] Tile clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3"
})
}
- fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logHandleClick(tileSpec: String, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
- str2 = StatusBarState.toString(statusBarState)
- str3 = toStateString(state)
+ int1 = eventId
}, {
- "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile handling click."
})
}
- fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int) {
+ fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
log(DEBUG, {
str1 = tileSpec
- int1 = statusBarState
+ int1 = eventId
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
}, {
- "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3"
+ })
+ }
+
+ fun logHandleSecondaryClick(tileSpec: String, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
+ }, {
+ "[$str1][$int1] Tile handling secondary click."
+ })
+ }
+
+ fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
+ str2 = StatusBarState.toString(statusBarState)
+ str3 = toStateString(state)
+ }, {
+ "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3"
+ })
+ }
+
+ fun logHandleLongClick(tileSpec: String, eventId: Int) {
+ log(DEBUG, {
+ str1 = tileSpec
+ int1 = eventId
+ }, {
+ "[$str1][$int1] Tile handling long click."
})
}
@@ -128,6 +155,54 @@
})
}
+ fun logOnViewAttached(orientation: Int, containerName: String) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = orientation
+ }, {
+ "onViewAttached: $str1 orientation $int1"
+ })
+ }
+
+ fun logOnViewDetached(orientation: Int, containerName: String) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = orientation
+ }, {
+ "onViewDetached: $str1 orientation $int1"
+ })
+ }
+
+ fun logOnConfigurationChanged(
+ lastOrientation: Int,
+ newOrientation: Int,
+ containerName: String
+ ) {
+ log(DEBUG, {
+ str1 = containerName
+ int1 = lastOrientation
+ int2 = newOrientation
+ }, {
+ "configuration change: $str1 orientation was $int1, now $int2"
+ })
+ }
+
+ fun logSwitchTileLayout(
+ after: Boolean,
+ before: Boolean,
+ force: Boolean,
+ containerName: String
+ ) {
+ log(DEBUG, {
+ str1 = containerName
+ bool1 = after
+ bool2 = before
+ bool3 = force
+ }, {
+ "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3"
+ })
+ }
+
private fun toStateString(state: Int): String {
return when (state) {
Tile.STATE_ACTIVE -> "active"
@@ -144,4 +219,4 @@
) {
buffer.log(TAG, logLevel, initializer, printer)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 740e12a..2cffe89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -105,6 +105,9 @@
private final FalsingManager mFalsingManager;
protected final QSLogger mQSLogger;
private volatile int mReadyState;
+ // Keeps track of the click event, to match it with the handling in the background thread
+ // Only read and modified in main thread (where click events come through).
+ private int mClickEventId = 0;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mStaleListener = new Object();
@@ -295,9 +298,11 @@
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
getInstanceId());
- mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
+ final int eventId = mClickEventId++;
+ mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
+ eventId);
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mHandler.obtainMessage(H.CLICK, view).sendToTarget();
+ mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget();
}
}
@@ -307,9 +312,10 @@
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
getInstanceId());
+ final int eventId = mClickEventId++;
mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
- mState.state);
- mHandler.obtainMessage(H.SECONDARY_CLICK, view).sendToTarget();
+ mState.state, eventId);
+ mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget();
}
@Override
@@ -319,8 +325,10 @@
mStatusBarStateController.getState())));
mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
getInstanceId());
- mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
- mHandler.obtainMessage(H.LONG_CLICK, view).sendToTarget();
+ final int eventId = mClickEventId++;
+ mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
+ eventId);
+ mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
}
public LogMaker populate(LogMaker logMaker) {
@@ -590,13 +598,16 @@
mContext, mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
+ mQSLogger.logHandleClick(mTileSpec, msg.arg1);
handleClick((View) msg.obj);
}
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
+ mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
handleSecondaryClick((View) msg.obj);
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
+ mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
handleLongClick((View) msg.obj);
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 438236d..30862b7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -638,12 +638,7 @@
// Listen for user setup
startTracking();
- screenLifecycle.addObserver(new ScreenLifecycle.Observer() {
- @Override
- public void onScreenTurnedOn() {
- notifyScreenTurnedOn();
- }
- });
+ screenLifecycle.addObserver(mLifecycleObserver);
// Connect to the service
updateEnabledState();
@@ -951,20 +946,55 @@
}
}
- /**
- * Notifies the Launcher that screen turned on and ready to use
- */
- public void notifyScreenTurnedOn() {
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onScreenTurnedOn();
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
+ /**
+ * Notifies the Launcher that screen turned on and ready to use
+ */
+ @Override
+ public void onScreenTurnedOn() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurnedOn();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
}
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call notifyScreenTurnedOn()", e);
}
- }
+
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOff() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOff();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
+ }
+ }
+
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOn(@NonNull Runnable ignored) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOn();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turning on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
+ }
+ }
+ };
void notifyToggleRecentApps() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 0a8e6e2..56a1874 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -39,7 +39,7 @@
ROUNDED_BOX,
ELLIPSE
}
-
+ //language=AGSL
companion object {
private const val SHADER_UNIFORMS = """uniform vec2 in_center;
uniform vec2 in_size;
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
index 0cacbc2..6de4648 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
@@ -17,6 +17,7 @@
/** A common utility functions that are used for computing [RippleShader]. */
class RippleShaderUtilLibrary {
+ //language=AGSL
companion object {
const val SHADER_LIB = """
float triangleNoise(vec2 n) {
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 83d9f2d..8b01201 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -39,7 +39,9 @@
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private lateinit var rippleShader: RippleShader
- private lateinit var rippleShape: RippleShape
+ lateinit var rippleShape: RippleShape
+ private set
+
private val ripplePaint = Paint()
var rippleInProgress: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
index 7f26146..5e256c6 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
@@ -17,6 +17,7 @@
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
+ //language=AGSL
companion object {
const val CIRCLE_SDF = """
float sdCircle(vec2 p, float r) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index beb54c8..4397d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -18,7 +18,6 @@
import android.net.Uri
import android.util.Log
-import android.view.WindowManager.ScreenshotType
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
@@ -37,13 +36,12 @@
private val controller: ScreenshotController,
) {
fun processRequest(
- @ScreenshotType type: Int,
- onSavedListener: Consumer<Uri>,
request: ScreenshotRequest,
+ onSavedListener: Consumer<Uri>,
callback: RequestCallback
) {
- if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
controller.handleImageAsScreenshot(
@@ -53,12 +51,12 @@
return
}
- when (type) {
+ when (request.type) {
TAKE_SCREENSHOT_FULLSCREEN ->
controller.takeScreenshotFullscreen(null, onSavedListener, callback)
TAKE_SCREENSHOT_SELECTED_REGION ->
controller.takeScreenshotPartial(null, onSavedListener, callback)
- else -> Log.w(TAG, "Invalid screenshot option: $type")
+ else -> Log.w(TAG, "Invalid screenshot option: ${request.type}")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f1f0223..7bf3217 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -229,11 +229,11 @@
if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
Log.d(TAG, "handleMessage: Using request processor");
- mProcessor.processRequest(msg.what, uriConsumer, screenshotRequest, requestCallback);
+ mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback);
return true;
}
- switch (msg.what) {
+ switch (screenshotRequest.getType()) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 13a5615..2a46776 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -6,7 +6,11 @@
import android.view.WindowInsets
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.*
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
@@ -171,33 +175,23 @@
private fun calculateBottomSpacing(): Paddings {
val containerPadding: Int
- var stackScrollMargin = notificationsBottomMargin
- if (splitShadeEnabled) {
- if (isGestureNavigation) {
- // only default cutout padding, taskbar always hides
- containerPadding = bottomCutoutInsets
- } else if (taskbarVisible) {
- // navigation buttons + visible taskbar means we're NOT on homescreen
- containerPadding = bottomStableInsets
- } else {
- // navigation buttons + hidden taskbar means we're on homescreen
- containerPadding = 0
- // we need extra margin for notifications as navigation buttons are below them
- stackScrollMargin = bottomStableInsets + notificationsBottomMargin
- }
+ val stackScrollMargin: Int
+ if (!splitShadeEnabled && (isQSCustomizing || isQSDetailShowing)) {
+ // Clear out bottom paddings/margins so the qs customization can be full height.
+ containerPadding = 0
+ stackScrollMargin = 0
+ } else if (isGestureNavigation) {
+ // only default cutout padding, taskbar always hides
+ containerPadding = bottomCutoutInsets
+ stackScrollMargin = notificationsBottomMargin
+ } else if (taskbarVisible) {
+ // navigation buttons + visible taskbar means we're NOT on homescreen
+ containerPadding = bottomStableInsets
+ stackScrollMargin = notificationsBottomMargin
} else {
- if (isQSCustomizing || isQSDetailShowing) {
- // Clear out bottom paddings/margins so the qs customization can be full height.
- containerPadding = 0
- stackScrollMargin = 0
- } else if (isGestureNavigation) {
- containerPadding = bottomCutoutInsets
- } else if (taskbarVisible) {
- containerPadding = bottomStableInsets
- } else {
- containerPadding = 0
- stackScrollMargin = bottomStableInsets + notificationsBottomMargin
- }
+ // navigation buttons + hidden taskbar means we're on homescreen
+ containerPadding = 0
+ stackScrollMargin = bottomStableInsets + notificationsBottomMargin
}
val qsContainerPadding = if (!(isQSCustomizing || isQSDetailShowing)) {
// We also want this padding in the bottom in these cases
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index decc02c..0898d63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -36,7 +36,7 @@
* remove notifications that appear on screen for a period of time and dismiss themselves at the
* appropriate time. These include heads up notifications and ambient pulses.
*/
-public abstract class AlertingNotificationManager implements NotificationLifetimeExtender {
+public abstract class AlertingNotificationManager {
private static final String TAG = "AlertNotifManager";
protected final Clock mClock = new Clock();
protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
@@ -47,14 +47,6 @@
mHandler = handler;
}
- /**
- * This is the list of entries that have already been removed from the
- * NotificationManagerService side, but we keep it to prevent the UI from looking weird and
- * will remove when possible. See {@link NotificationLifetimeExtender}
- */
- protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>();
-
- protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
protected int mMinimumDisplayTime;
protected int mAutoDismissNotificationDecay;
private final Handler mHandler;
@@ -209,12 +201,6 @@
onAlertEntryRemoved(alertEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
alertEntry.reset();
- if (mExtendedLifetimeAlertEntries.contains(entry)) {
- if (mNotificationLifetimeFinishedCallback != null) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
- }
- mExtendedLifetimeAlertEntries.remove(entry);
- }
}
/**
@@ -243,19 +229,6 @@
|| alertEntry.mEntry.isRowDismissed();
}
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // NotificationLifetimeExtender Methods
-
- @Override
- public void setCallback(NotificationSafeToRemoveCallback callback) {
- mNotificationLifetimeFinishedCallback = callback;
- }
-
- @Override
- public boolean shouldExtendLifetime(NotificationEntry entry) {
- return !canRemoveImmediately(entry.getKey());
- }
-
/**
* @param key
* @return true if the entry is pinned
@@ -280,20 +253,6 @@
return 0;
}
- @Override
- public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
- if (shouldExtend) {
- mExtendedLifetimeAlertEntries.add(entry);
- // We need to make sure that entries are stopping to alert eventually, let's remove
- // this as soon as possible.
- AlertEntry alertEntry = mAlertEntries.get(entry.getKey());
- alertEntry.removeAsSoonAsPossible();
- } else {
- mExtendedLifetimeAlertEntries.remove(entry);
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////
-
protected class AlertEntry implements Comparable<AlertEntry> {
@Nullable public NotificationEntry mEntry;
public long mPostTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ca14728..c983644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -27,6 +27,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -36,7 +37,6 @@
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
-import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -123,6 +123,8 @@
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+ public static final long DEFAULT_HIDE_DELAY_MS =
+ 3500 + KeyguardIndicationTextView.Y_IN_DURATION;
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -140,7 +142,6 @@
protected final @Main DelayableExecutor mExecutor;
protected final @Background DelayableExecutor mBackgroundExecutor;
private final LockPatternUtils mLockPatternUtils;
- private final IActivityManager mIActivityManager;
private final FalsingManager mFalsingManager;
private final KeyguardBypassController mKeyguardBypassController;
private final AccessibilityManager mAccessibilityManager;
@@ -155,6 +156,7 @@
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
private CharSequence mBiometricMessage;
+ private CharSequence mBiometricMessageFollowUp;
protected ColorStateList mInitialTextColorState;
private boolean mVisible;
private boolean mOrganizationOwnedDevice;
@@ -171,7 +173,7 @@
private int mBatteryLevel;
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
- private String mMessageToShowOnScreenOn;
+ private String mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceHelpMsgIdsToShow;
private boolean mInited;
@@ -189,11 +191,11 @@
private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
- if (mMessageToShowOnScreenOn != null) {
- showBiometricMessage(mMessageToShowOnScreenOn);
+ if (mBiometricErrorMessageToShowOnScreenOn != null) {
+ showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
// We want to keep this message around in case the screen was off
- hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
- mMessageToShowOnScreenOn = null;
+ hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
+ mBiometricErrorMessageToShowOnScreenOn = null;
}
}
};
@@ -219,7 +221,6 @@
FalsingManager falsingManager,
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
- IActivityManager iActivityManager,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager) {
mContext = context;
@@ -236,7 +237,6 @@
mExecutor = executor;
mBackgroundExecutor = bgExecutor;
mLockPatternUtils = lockPatternUtils;
- mIActivityManager = iActivityManager;
mFalsingManager = falsingManager;
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
@@ -498,8 +498,23 @@
.build(),
true
);
+ if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
+ mRotateTextViewController.updateIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ new KeyguardIndication.Builder()
+ .setMessage(mBiometricMessageFollowUp)
+ .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+ .setTextColor(mInitialTextColorState)
+ .build(),
+ true
+ );
+ } else {
+ mRotateTextViewController.hideIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
+ }
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
}
}
@@ -719,38 +734,45 @@
private void showTransientIndication(CharSequence transientIndication) {
mTransientIndication = transientIndication;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
- hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+ hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
updateTransient();
}
- /**
- * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
- */
- public void showBiometricMessage(int biometricMessage) {
- showBiometricMessage(mContext.getResources().getString(biometricMessage));
+ private void showBiometricMessage(CharSequence biometricMessage) {
+ showBiometricMessage(biometricMessage, null);
}
/**
- * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}.
+ * Shows {@param biometricMessage} and {@param biometricMessageFollowUp}
+ * until they are hidden by {@link #hideBiometricMessage}. Messages are rotated through
+ * by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message
+ * logic.
*/
- private void showBiometricMessage(CharSequence biometricMessage) {
+ private void showBiometricMessage(CharSequence biometricMessage,
+ CharSequence biometricMessageFollowUp) {
if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
return;
}
mBiometricMessage = biometricMessage;
+ mBiometricMessageFollowUp = biometricMessageFollowUp;
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
- hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+ hideBiometricMessageDelayed(
+ mBiometricMessageFollowUp != null
+ ? DEFAULT_HIDE_DELAY_MS * 2
+ : DEFAULT_HIDE_DELAY_MS
+ );
updateBiometricMessage();
}
private void hideBiometricMessage() {
- if (mBiometricMessage != null) {
+ if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
mBiometricMessage = null;
+ mBiometricMessageFollowUp = null;
mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
updateBiometricMessage();
}
@@ -789,9 +811,9 @@
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
- CharSequence newIndication = null;
+ CharSequence newIndication;
if (!TextUtils.isEmpty(mBiometricMessage)) {
- newIndication = mBiometricMessage;
+ newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
} else if (!TextUtils.isEmpty(mTransientIndication)) {
newIndication = mTransientIndication;
} else if (!mBatteryPresent) {
@@ -909,15 +931,21 @@
|| mAccessibilityManager.isTouchExplorationEnabled();
if (udfpsSupported && faceAuthenticated) { // co-ex
if (a11yEnabled) {
- showBiometricMessage(mContext.getString(
- R.string.keyguard_face_successful_unlock_swipe));
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_successful_unlock),
+ mContext.getString(R.string.keyguard_unlock)
+ );
} else {
- showBiometricMessage(mContext.getString(
- R.string.keyguard_face_successful_unlock_press));
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_successful_unlock),
+ mContext.getString(R.string.keyguard_unlock_press)
+ );
}
} else if (faceAuthenticated) { // face-only
- showBiometricMessage(mContext.getString(
- R.string.keyguard_face_successful_unlock_swipe));
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_successful_unlock),
+ mContext.getString(R.string.keyguard_unlock)
+ );
} else if (udfpsSupported) { // udfps-only
if (a11yEnabled) {
showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
@@ -943,10 +971,11 @@
pw.println(" mPowerCharged: " + mPowerCharged);
pw.println(" mChargingSpeed: " + mChargingSpeed);
pw.println(" mChargingWattage: " + mChargingWattage);
- pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
+ pw.println(" mMessageToShowOnScreenOn: " + mBiometricErrorMessageToShowOnScreenOn);
pw.println(" mDozing: " + mDozing);
pw.println(" mTransientIndication: " + mTransientIndication);
pw.println(" mBiometricMessage: " + mBiometricMessage);
+ pw.println(" mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
pw.println(" mBatteryLevel: " + mBatteryLevel);
pw.println(" mBatteryPresent: " + mBatteryPresent);
pw.println(" AOD text: " + (
@@ -958,8 +987,6 @@
}
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
- public static final int HIDE_DELAY_MS = 5000;
-
@Override
public void onTimeChanged() {
if (mVisible) {
@@ -1077,7 +1104,7 @@
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
showBiometricMessage(errString);
} else {
- mMessageToShowOnScreenOn = errString;
+ mBiometricErrorMessageToShowOnScreenOn = errString;
}
}
@@ -1139,7 +1166,7 @@
// Let's hide any previous messages when authentication starts, otherwise
// multiple auth attempts would overlap.
hideBiometricMessage();
- mMessageToShowOnScreenOn = null;
+ mBiometricErrorMessageToShowOnScreenOn = null;
}
}
@@ -1179,7 +1206,7 @@
@Override
public void onRequireUnlockForNfc() {
showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
- hideTransientIndicationDelayed(HIDE_DELAY_MS);
+ hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
deleted file mode 100644
index 48e2923..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.android.systemui.statusbar;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * Interface for anything that may need to keep notifications managed even after
- * {@link NotificationListener} removes it. The lifetime extender is in charge of performing the
- * callback when the notification is then safe to remove.
- */
-public interface NotificationLifetimeExtender {
-
- /**
- * Set the handler to callback to when the notification is safe to remove.
- *
- * @param callback the handler to callback
- */
- void setCallback(@NonNull NotificationSafeToRemoveCallback callback);
-
- /**
- * Determines whether or not the extender needs the notification kept after removal.
- *
- * @param entry the entry containing the notification to check
- * @return true if the notification lifetime should be extended
- */
- boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
-
- /**
- * It's possible that a notification was canceled before it ever became visible. This callback
- * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
- * service is canceled too quickly but we still want to make sure a FGS notification shows.
- * @param pendingEntry the canceled (but pending) entry
- * @return true if the notification lifetime should be extended
- */
- default boolean shouldExtendLifetimeForPendingNotification(
- @NonNull NotificationEntry pendingEntry) {
- return false;
- }
-
- /**
- * Sets whether or not the lifetime should be managed by the extender. In practice, if
- * shouldManage is true, this is where the extender starts managing the entry internally and is
- * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
- * when the entry is safe to remove. If shouldManage is false, the extender no longer needs to
- * worry about it (either because we will be removing it anyway or the entry is no longer
- * removed due to an update).
- *
- * @param entry the entry that needs an extended lifetime
- * @param shouldManage true if the extender should manage the entry now, false otherwise
- */
- void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage);
-
- /**
- * The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
- */
- interface NotificationSafeToRemoveCallback {
- /**
- * Called when the lifetime extender determines it's safe to remove.
- *
- * @param key key of the entry that is now safe to remove
- */
- void onSafeToRemove(String key);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 78b3b0a..d74d408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -32,7 +31,6 @@
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
@@ -47,12 +45,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
@@ -75,7 +71,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Consumer;
import dagger.Lazy;
@@ -100,8 +95,6 @@
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final SmartReplyController mSmartReplyController;
private final NotificationVisibilityProvider mVisibilityProvider;
- private final NotificationEntryManager mEntryManager;
- private final Handler mMainHandler;
private final ActionClickLogger mLogger;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -110,7 +103,6 @@
protected final NotifPipelineFlags mNotifPipelineFlags;
private final UserManager mUserManager;
private final KeyguardManager mKeyguardManager;
- private final RemoteInputNotificationRebuilder mRebuilder;
private final StatusBarStateController mStatusBarStateController;
private final RemoteInputUriController mRemoteInputUriController;
private final NotificationClickNotifier mClickNotifier;
@@ -265,10 +257,8 @@
SmartReplyController smartReplyController,
NotificationVisibilityProvider visibilityProvider,
NotificationEntryManager notificationEntryManager,
- RemoteInputNotificationRebuilder rebuilder,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
StatusBarStateController statusBarStateController,
- @Main Handler mainHandler,
RemoteInputUriController remoteInputUriController,
NotificationClickNotifier clickNotifier,
ActionClickLogger logger,
@@ -278,14 +268,11 @@
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
mVisibilityProvider = visibilityProvider;
- mEntryManager = notificationEntryManager;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
- mMainHandler = mainHandler;
mLogger = logger;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mRebuilder = rebuilder;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
@@ -788,238 +775,4 @@
/** Called when the RemoteInputController is attached to the manager */
void setRemoteInputController(@NonNull RemoteInputController remoteInputController);
}
-
- @VisibleForTesting
- protected class LegacyRemoteInputLifetimeExtender implements RemoteInputListener, Dumpable {
-
- /**
- * How long to wait before auto-dismissing a notification that was kept for remote input,
- * and has now sent a remote input. We auto-dismiss, because the app may not see a reason to
- * cancel these given that they technically don't exist anymore. We wait a bit in case the
- * app issues an update.
- */
- private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
-
- /**
- * Notifications that are already removed but are kept around because we want to show the
- * remote input history. See {@link RemoteInputHistoryExtender} and
- * {@link SmartReplyHistoryExtender}.
- */
- protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>();
-
- /**
- * Notifications that are already removed but are kept around because the remote input is
- * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
- */
- protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
- new ArraySet<>();
-
- protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
- mNotificationLifetimeFinishedCallback;
-
- protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders =
- new ArrayList<>();
- private RemoteInputController mRemoteInputController;
-
- LegacyRemoteInputLifetimeExtender() {
- addLifetimeExtenders();
- }
-
- /**
- * Adds all the notification lifetime extenders. Each extender represents a reason for the
- * NotificationRemoteInputManager to keep a notification lifetime extended.
- */
- protected void addLifetimeExtenders() {
- mLifetimeExtenders.add(new RemoteInputHistoryExtender());
- mLifetimeExtenders.add(new SmartReplyHistoryExtender());
- mLifetimeExtenders.add(new RemoteInputActiveExtender());
- }
-
- @Override
- public void setRemoteInputController(@NonNull RemoteInputController remoteInputController) {
- mRemoteInputController= remoteInputController;
- }
-
- @Override
- public void onRemoteInputSent(@NonNull NotificationEntry entry) {
- if (FORCE_REMOTE_INPUT_HISTORY
- && isNotificationKeptForRemoteInputHistory(entry.getKey())) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- } else if (mEntriesKeptForRemoteInputActive.contains(entry)) {
- // We're currently holding onto this notification, but from the apps point of
- // view it is already canceled, so we'll need to cancel it on the apps behalf
- // after sending - unless the app posts an update in the mean time, so wait a
- // bit.
- mMainHandler.postDelayed(() -> {
- if (mEntriesKeptForRemoteInputActive.remove(entry)) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- }
- }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
- }
- }
-
- @Override
- public void onPanelCollapsed() {
- for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
- NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
- if (mRemoteInputController != null) {
- mRemoteInputController.removeRemoteInput(entry, null);
- }
- if (mNotificationLifetimeFinishedCallback != null) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- }
- }
- mEntriesKeptForRemoteInputActive.clear();
- }
-
- @Override
- public boolean isNotificationKeptForRemoteInputHistory(@NonNull String key) {
- return mKeysKeptForRemoteInputHistory.contains(key);
- }
-
- @Override
- public void releaseNotificationIfKeptForRemoteInputHistory(
- @NonNull NotificationEntry entry) {
- final String key = entry.getKey();
- if (isNotificationKeptForRemoteInputHistory(key)) {
- mMainHandler.postDelayed(() -> {
- if (isNotificationKeptForRemoteInputHistory(key)) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
- }
- }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
- }
- }
-
- @VisibleForTesting
- public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
- return mEntriesKeptForRemoteInputActive;
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw,
- @NonNull String[] args) {
- pw.println("LegacyRemoteInputLifetimeExtender:");
- pw.print(" mKeysKeptForRemoteInputHistory: ");
- pw.println(mKeysKeptForRemoteInputHistory);
- pw.print(" mEntriesKeptForRemoteInputActive: ");
- pw.println(mEntriesKeptForRemoteInputActive);
- }
-
- /**
- * NotificationRemoteInputManager has multiple reasons to keep notification lifetime
- * extended so we implement multiple NotificationLifetimeExtenders
- */
- protected abstract class RemoteInputExtender implements NotificationLifetimeExtender {
- @Override
- public void setCallback(NotificationSafeToRemoveCallback callback) {
- if (mNotificationLifetimeFinishedCallback == null) {
- mNotificationLifetimeFinishedCallback = callback;
- }
- }
- }
-
- /**
- * Notification is kept alive as it was cancelled in response to a remote input interaction.
- * This allows us to show what you replied and allows you to continue typing into it.
- */
- protected class RemoteInputHistoryExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return shouldKeepForRemoteInputHistory(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- StatusBarNotification newSbn = mRebuilder.rebuildForRemoteInputReply(entry);
- entry.onRemoteInputInserted();
-
- if (newSbn == null) {
- return;
- }
-
- mEntryManager.updateNotification(newSbn, null);
-
- // Ensure the entry hasn't already been removed. This can happen if there is an
- // inflation exception while updating the remote history
- if (entry.isRemoved()) {
- return;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around after sending remote input "
- + entry.getKey());
- }
-
- mKeysKeptForRemoteInputHistory.add(entry.getKey());
- } else {
- mKeysKeptForRemoteInputHistory.remove(entry.getKey());
- }
- }
- }
-
- /**
- * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but
- * with {@link SmartReplyController} specific logic
- */
- protected class SmartReplyHistoryExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return shouldKeepForSmartReplyHistory(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- StatusBarNotification newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry);
-
- if (newSbn == null) {
- return;
- }
-
- mEntryManager.updateNotification(newSbn, null);
-
- if (entry.isRemoved()) {
- return;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around after sending smart reply "
- + entry.getKey());
- }
-
- mKeysKeptForRemoteInputHistory.add(entry.getKey());
- } else {
- mKeysKeptForRemoteInputHistory.remove(entry.getKey());
- mSmartReplyController.stopSending(entry);
- }
- }
- }
-
- /**
- * Notification is kept alive because the user is still using the remote input
- */
- protected class RemoteInputActiveExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return isRemoteInputActive(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around while remote input active "
- + entry.getKey());
- }
- mEntriesKeptForRemoteInputActive.add(entry);
- } else {
- mEntriesKeptForRemoteInputActive.remove(entry);
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 0951e82..8752f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -115,10 +115,8 @@
smartReplyController,
visibilityProvider,
notificationEntryManager,
- rebuilder,
centralSurfacesOptionalLazy,
statusBarStateController,
- mainHandler,
remoteInputUriController,
clickNotifier,
actionClickLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 7583a98..e2f87b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -15,66 +15,22 @@
*/
package com.android.systemui.statusbar.notification;
-import static android.service.notification.NotificationListenerService.REASON_ERROR;
-
-import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRankerStub;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.util.Assert;
-import com.android.systemui.util.Compile;
-import com.android.systemui.util.leak.LeakDetector;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-import dagger.Lazy;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of
@@ -90,33 +46,10 @@
* inflated, an entry moves into the active state, where it _could_ potentially be shown to the
* user. After an entry makes its way into the active state, we sort and filter the entire set to
* repopulate the visible set.
- *
- * There are a few different things that other classes may be interested in, and most of them
- * involve the current set of notifications. Here's a brief overview of things you may want to know:
- * @see #getVisibleNotifications() for the visible set
- * @see #getActiveNotificationUnfiltered(String) to check if a key exists
- * @see #getPendingNotificationsIterator() for an iterator over the pending notifications
- * @see #getPendingOrActiveNotif(String) to find a notification exists for that key in any list
- * @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns
*/
-public class NotificationEntryManager implements
- CommonNotifCollection,
- Dumpable,
- VisualStabilityManager.Callback {
+public class NotificationEntryManager implements VisualStabilityManager.Callback {
private final NotificationEntryManagerLogger mLogger;
- private final NotificationGroupManagerLegacy mGroupManager;
- private final NotifPipelineFlags mNotifPipelineFlags;
- private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
- private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
- private final LeakDetector mLeakDetector;
- private final IStatusBarService mStatusBarService;
- private final DumpManager mDumpManager;
- private final Executor mBgExecutor;
-
- private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
- private final Set<NotificationEntry> mReadOnlyAllNotifications =
- Collections.unmodifiableSet(mAllNotifications);
/** Pending notifications are ones awaiting inflation */
@VisibleForTesting
@@ -129,96 +62,14 @@
/** This is the list of "active notifications for this user in this context" */
@VisibleForTesting
protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
- private final List<NotificationEntry> mReadOnlyNotifications =
- Collections.unmodifiableList(mSortedAndFiltered);
-
- private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
- new ArrayMap<>();
-
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
-
- private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
- private RankingMap mLatestRankingMap;
-
- @VisibleForTesting
- final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
- = new ArrayList<>();
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
- private final List<NotificationRemoveInterceptor> mRemoveInterceptors = new ArrayList<>();
/**
* Injected constructor. See {@link NotificationsModule}.
*/
- public NotificationEntryManager(
- NotificationEntryManagerLogger logger,
- NotificationGroupManagerLegacy groupManager,
- NotifPipelineFlags notifPipelineFlags,
- Lazy<NotificationRowBinder> notificationRowBinderLazy,
- Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
- LeakDetector leakDetector,
- IStatusBarService statusBarService,
- DumpManager dumpManager,
- @Background Executor bgExecutor
- ) {
+ public NotificationEntryManager(NotificationEntryManagerLogger logger) {
mLogger = logger;
- mGroupManager = groupManager;
- mNotifPipelineFlags = notifPipelineFlags;
- mNotificationRowBinderLazy = notificationRowBinderLazy;
- mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
- mLeakDetector = leakDetector;
- mStatusBarService = statusBarService;
- mDumpManager = dumpManager;
- mBgExecutor = bgExecutor;
- }
-
- /** Once called, the NEM will start processing notification events from system server. */
- public void initialize(
- NotificationListener notificationListener,
- LegacyNotificationRanker ranker) {
- mRanker = ranker;
- notificationListener.addNotificationHandler(mNotifListener);
- mDumpManager.registerDumpable(this);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("NotificationEntryManager state:");
- pw.println(" mAllNotifications=");
- if (mAllNotifications.size() == 0) {
- pw.println("null");
- } else {
- int i = 0;
- for (NotificationEntry entry : mAllNotifications) {
- dumpEntry(pw, " ", i, entry);
- i++;
- }
- }
- pw.print(" mPendingNotifications=");
- if (mPendingNotifications.size() == 0) {
- pw.println("null");
- } else {
- for (NotificationEntry entry : mPendingNotifications.values()) {
- pw.println(entry.getSbn());
- }
- }
- pw.println(" Remove interceptors registered:");
- for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
- pw.println(" " + interceptor.getClass().getSimpleName());
- }
- pw.println(" Lifetime extenders registered:");
- for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
- pw.println(" " + extender.getClass().getSimpleName());
- }
- pw.println(" Lifetime-extended notifications:");
- if (mRetainedNotifications.isEmpty()) {
- pw.println(" None");
- } else {
- for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry
- : mRetainedNotifications.entrySet()) {
- pw.println(" " + entry.getKey().getSbn() + " retained by "
- + entry.getValue().getClass().getName());
- }
- }
}
/** Adds a {@link NotificationEntryListener}. */
@@ -234,501 +85,12 @@
mNotificationEntryListeners.remove(listener);
}
- /** Add a {@link NotificationRemoveInterceptor}. */
- public void addNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
- mRemoveInterceptors.add(interceptor);
- }
-
- /** Remove a {@link NotificationRemoveInterceptor} */
- public void removeNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
- mRemoveInterceptors.remove(interceptor);
- }
-
- /** Adds multiple {@link NotificationLifetimeExtender}s. */
- public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
- for (NotificationLifetimeExtender extender : extenders) {
- addNotificationLifetimeExtender(extender);
- }
- }
-
- /** Adds a {@link NotificationLifetimeExtender}. */
- public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
- mNotificationLifetimeExtenders.add(extender);
- extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
- UNDEFINED_DISMISS_REASON));
- }
-
@Override
public void onChangeAllowed() {
updateNotifications("reordering is now allowed");
}
/**
- * User requests a notification to be removed.
- *
- * @param n the notification to remove.
- * @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
- * or 0 if unknown.
- */
- public void performRemoveNotification(
- StatusBarNotification n,
- @NonNull DismissedByUserStats stats,
- int reason
- ) {
- removeNotificationInternal(
- n.getKey(),
- null,
- stats.notificationVisibility,
- false /* forceRemove */,
- stats,
- reason);
- }
-
- private NotificationVisibility obtainVisibility(String key) {
- NotificationEntry e = mActiveNotifications.get(key);
- final int rank;
- if (e != null) {
- rank = e.getRanking().getRank();
- } else {
- rank = 0;
- }
-
- final int count = mActiveNotifications.size();
- NotificationVisibility.NotificationLocation location =
- NotificationLogger.getNotificationLocation(getActiveNotificationUnfiltered(key));
- return NotificationVisibility.obtain(key, rank, count, true, location);
- }
-
- private void abortExistingInflation(String key, String reason) {
- if (mPendingNotifications.containsKey(key)) {
- NotificationEntry entry = mPendingNotifications.get(key);
- entry.abortTask();
- mPendingNotifications.remove(key);
- mLogger.logInflationAborted(key, "pending", reason);
- }
- NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
- if (addedEntry != null) {
- addedEntry.abortTask();
- mLogger.logInflationAborted(key, "active", reason);
- }
- }
-
- /**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- private void handleInflationException(StatusBarNotification n, Exception e) {
- removeNotificationInternal(
- n.getKey(),
- null,
- null,
- true /* forceRemove */,
- null /* dismissedByUserStats */,
- REASON_ERROR);
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onInflationError(n, e);
- }
- }
-
- private final InflationCallback mInflationCallback = new InflationCallback() {
- @Override
- public void handleInflationException(NotificationEntry entry, Exception e) {
- Trace.beginSection("NotificationEntryManager.handleInflationException");
- NotificationEntryManager.this.handleInflationException(entry.getSbn(), e);
- Trace.endSection();
- }
-
- @Override
- public void onAsyncInflationFinished(NotificationEntry entry) {
- Trace.beginSection("NotificationEntryManager.onAsyncInflationFinished");
- mPendingNotifications.remove(entry.getKey());
- // If there was an async task started after the removal, we don't want to add it back to
- // the list, otherwise we might get leaks.
- if (!entry.isRowRemoved()) {
- boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
- mLogger.logNotifInflated(entry.getKey(), isNew);
- if (isNew) {
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryInflated(entry);
- }
- addActiveNotification(entry);
- updateNotifications("onAsyncInflationFinished");
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onNotificationAdded(entry);
- }
- } else {
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryReinflated(entry);
- }
- }
- }
- Trace.endSection();
- }
- };
-
- private final NotificationHandler mNotifListener = new NotificationHandler() {
- @Override
- public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
- final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
- if (isUpdateToInflatedNotif) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
- }
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
- removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
- }
-
- @Override
- public void onNotificationRemoved(
- StatusBarNotification sbn,
- RankingMap rankingMap,
- int reason) {
- removeNotification(sbn.getKey(), rankingMap, reason);
- }
-
- @Override
- public void onNotificationRankingUpdate(RankingMap rankingMap) {
- updateNotificationRanking(rankingMap);
- }
-
- @Override
- public void onNotificationsInitialized() {
- }
-
- @Override
- public void onNotificationChannelModified(
- String pkgName,
- UserHandle user,
- NotificationChannel channel,
- int modificationType) {
- notifyChannelModified(pkgName, user, channel, modificationType);
- }
- };
-
- /**
- * Equivalent to the old NotificationData#add
- * @param entry - an entry which is prepared for display
- */
- private void addActiveNotification(NotificationEntry entry) {
- Assert.isMainThread();
-
- mActiveNotifications.put(entry.getKey(), entry);
- mGroupManager.onEntryAdded(entry);
- updateRankingAndSort(mRanker.getRankingMap(), "addEntryInternalInternal");
- }
-
- /**
- * Available so that tests can directly manipulate the list of active notifications easily
- *
- * @param entry the entry to add directly to the visible notification map
- */
- @VisibleForTesting
- public void addActiveNotificationForTest(NotificationEntry entry) {
- mActiveNotifications.put(entry.getKey(), entry);
- mGroupManager.onEntryAdded(entry);
-
- reapplyFilterAndSort("addVisibleNotification");
- }
-
- @VisibleForTesting
- protected void removeNotification(String key, RankingMap ranking, int reason) {
- removeNotificationInternal(
- key,
- ranking,
- obtainVisibility(key),
- false /* forceRemove */,
- null /* dismissedByUserStats */,
- reason);
- }
-
- /**
- * Internally remove a notification because system server has reported the notification
- * should be removed OR the user has manually dismissed the notification
- * @param dismissedByUserStats non-null if the user manually dismissed the notification
- */
- private void removeNotificationInternal(
- String key,
- @Nullable RankingMap ranking,
- @Nullable NotificationVisibility visibility,
- boolean forceRemove,
- DismissedByUserStats dismissedByUserStats,
- int reason) {
- Trace.beginSection("NotificationEntryManager.removeNotificationInternal");
-
- final NotificationEntry entry = getActiveNotificationUnfiltered(key);
-
- for (NotificationRemoveInterceptor interceptor : mRemoveInterceptors) {
- if (interceptor.onNotificationRemoveRequested(key, entry, reason)) {
- // Remove intercepted; log and skip
- mLogger.logRemovalIntercepted(key);
- Trace.endSection();
- return;
- }
- }
-
- boolean lifetimeExtended = false;
-
- // Notification was canceled before it got inflated
- if (entry == null) {
- NotificationEntry pendingEntry = mPendingNotifications.get(key);
- if (pendingEntry != null) {
- for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
- if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
- extendLifetime(pendingEntry, extender);
- lifetimeExtended = true;
- mLogger.logLifetimeExtended(key, extender.getClass().getName(), "pending");
- }
- }
- if (!lifetimeExtended) {
- // At this point, we are guaranteed the notification will be removed
- abortExistingInflation(key, "removeNotification");
- // Fix for b/201097913: NotifCollectionListener#onEntryRemoved specifies that
- // #onEntryRemoved should be called when a notification is cancelled,
- // regardless of whether the notification was pending or active.
- // Note that mNotificationEntryListeners are NOT notified of #onEntryRemoved
- // because for that interface, #onEntryRemoved should only be called for
- // active entries, NOT pending ones.
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryRemoved(pendingEntry, REASON_UNKNOWN);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryCleanUp(pendingEntry);
- }
- mAllNotifications.remove(pendingEntry);
- mLeakDetector.trackGarbage(pendingEntry);
- }
- }
- } else {
- // If a manager needs to keep the notification around for whatever reason, we
- // keep the notification
- boolean entryDismissed = entry.isRowDismissed();
- if (!forceRemove && !entryDismissed) {
- for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
- if (extender.shouldExtendLifetime(entry)) {
- mLatestRankingMap = ranking;
- extendLifetime(entry, extender);
- lifetimeExtended = true;
- mLogger.logLifetimeExtended(key, extender.getClass().getName(), "active");
- break;
- }
- }
- }
-
- if (!lifetimeExtended) {
- // At this point, we are guaranteed the notification will be removed
- abortExistingInflation(key, "removeNotification");
- mAllNotifications.remove(entry);
-
- // Ensure any managers keeping the lifetime extended stop managing the entry
- cancelLifetimeExtension(entry);
-
- if (entry.rowExists()) {
- entry.removeRow();
- }
-
- // Let's remove the children if this was a summary
- handleGroupSummaryRemoved(key);
- removeVisibleNotification(key);
- updateNotifications("removeNotificationInternal");
- final boolean removedByUser = dismissedByUserStats != null;
-
- mLogger.logNotifRemoved(entry.getKey(), removedByUser);
- if (removedByUser && visibility != null) {
- sendNotificationRemovalToServer(entry.getSbn(), dismissedByUserStats);
- }
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryRemoved(entry, visibility, removedByUser, reason);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- // NEM doesn't have a good knowledge of reasons so defaulting to unknown.
- listener.onEntryRemoved(entry, REASON_UNKNOWN);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryCleanUp(entry);
- }
- mLeakDetector.trackGarbage(entry);
- }
- }
- Trace.endSection();
- }
-
- private void sendNotificationRemovalToServer(
- StatusBarNotification notification,
- DismissedByUserStats dismissedByUserStats) {
- mBgExecutor.execute(() -> {
- try {
- mStatusBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getUser().getIdentifier(),
- notification.getKey(),
- dismissedByUserStats.dismissalSurface,
- dismissedByUserStats.dismissalSentiment,
- dismissedByUserStats.notificationVisibility);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- });
- }
-
- /**
- * Ensures that the group children are cancelled immediately when the group summary is cancelled
- * instead of waiting for the notification manager to send all cancels. Otherwise this could
- * lead to flickers.
- *
- * This also ensures that the animation looks nice and only consists of a single disappear
- * animation instead of multiple.
- * @param key the key of the notification was removed
- *
- */
- private void handleGroupSummaryRemoved(String key) {
- NotificationEntry entry = getActiveNotificationUnfiltered(key);
- if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
- if (entry.getSbn().getOverrideGroupKey() != null && !entry.isRowDismissed()) {
- // We don't want to remove children for autobundled notifications as they are not
- // always cancelled. We only remove them if they were dismissed by the user.
- return;
- }
- List<NotificationEntry> childEntries = entry.getAttachedNotifChildren();
- if (childEntries == null) {
- return;
- }
- for (int i = 0; i < childEntries.size(); i++) {
- NotificationEntry childEntry = childEntries.get(i);
- boolean isForeground = (entry.getSbn().getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- boolean keepForReply =
- mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
- || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
- if (isForeground || keepForReply) {
- // the child is a foreground service notification which we can't remove or it's
- // a child we're keeping around for reply!
- continue;
- }
- childEntry.setKeepInParent(true);
- // we need to set this state earlier as otherwise we might generate some weird
- // animations
- childEntry.removeRow();
- }
- }
- }
-
- private void addNotificationInternal(
- StatusBarNotification notification,
- RankingMap rankingMap) throws InflationException {
- Trace.beginSection("NotificationEntryManager.addNotificationInternal");
- String key = notification.getKey();
- if (DEBUG) {
- Log.d(TAG, "addNotification key=" + key);
- }
-
- updateRankingAndSort(rankingMap, "addNotificationInternal");
-
- Ranking ranking = new Ranking();
- rankingMap.getRanking(key, ranking);
-
- NotificationEntry entry = mPendingNotifications.get(key);
- if (entry != null) {
- entry.setSbn(notification);
- entry.setRanking(ranking);
- } else {
- entry = new NotificationEntry(
- notification,
- ranking,
- SystemClock.uptimeMillis());
- mAllNotifications.add(entry);
- mLeakDetector.trackInstance(entry);
-
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryInit(entry);
- }
- }
-
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryBind(entry, notification);
- }
-
- mPendingNotifications.put(key, entry);
- mLogger.logNotifAdded(entry.getKey());
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onPendingEntryAdded(entry);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryAdded(entry);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingApplied();
- }
- Trace.endSection();
- }
-
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
- try {
- addNotificationInternal(notification, ranking);
- } catch (InflationException e) {
- handleInflationException(notification, e);
- }
- }
-
- private void updateNotificationInternal(StatusBarNotification notification,
- RankingMap ranking) throws InflationException {
- Trace.beginSection("NotificationEntryManager.updateNotificationInternal");
- if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
- final String key = notification.getKey();
- abortExistingInflation(key, "updateNotification");
- final NotificationEntry entry = getActiveNotificationUnfiltered(key);
- if (entry == null) {
- Trace.endSection();
- return;
- }
-
- // Notification is updated so it is essentially re-added and thus alive again. Don't need
- // to keep its lifetime extended.
- cancelLifetimeExtension(entry);
-
- updateRankingAndSort(ranking, "updateNotificationInternal");
- StatusBarNotification oldSbn = entry.getSbn();
- entry.setSbn(notification);
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryBind(entry, notification);
- }
- mGroupManager.onEntryUpdated(entry, oldSbn);
-
- mLogger.logNotifUpdated(entry.getKey());
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onPreEntryUpdated(entry);
- }
- final boolean fromSystem = ranking != null;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryUpdated(entry, fromSystem);
- }
-
- updateNotifications("updateNotificationInternal");
-
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onPostEntryUpdated(entry);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingApplied();
- }
- Trace.endSection();
- }
-
- public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
- try {
- updateNotificationInternal(notification, ranking);
- } catch (InflationException e) {
- handleInflationException(notification, e);
- }
- }
-
- /**
* Update the notifications
* @param reason why the notifications are updating
*/
@@ -736,128 +98,6 @@
mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
}
- public void updateNotificationRanking(RankingMap rankingMap) {
- Trace.beginSection("NotificationEntryManager.updateNotificationRanking");
- List<NotificationEntry> entries = new ArrayList<>();
- entries.addAll(getVisibleNotifications());
- entries.addAll(mPendingNotifications.values());
-
- // Has a copy of the current UI adjustments.
- ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
- ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
- for (NotificationEntry entry : entries) {
- NotificationUiAdjustment adjustment =
- NotificationUiAdjustment.extractFromNotificationEntry(entry);
- oldAdjustments.put(entry.getKey(), adjustment);
- oldImportances.put(entry.getKey(), entry.getImportance());
- }
-
- // Populate notification entries from the new rankings.
- updateRankingAndSort(rankingMap, "updateNotificationRanking");
- updateRankingOfPendingNotifications(rankingMap);
-
- // By comparing the old and new UI adjustments, reinflate the view accordingly.
- for (NotificationEntry entry : entries) {
- mNotificationRowBinderLazy.get()
- .onNotificationRankingUpdated(
- entry,
- oldImportances.get(entry.getKey()),
- oldAdjustments.get(entry.getKey()),
- NotificationUiAdjustment.extractFromNotificationEntry(entry),
- mInflationCallback);
- }
-
- updateNotifications("updateNotificationRanking");
-
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onNotificationRankingUpdated(rankingMap);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingUpdate(rankingMap);
- }
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingApplied();
- }
- Trace.endSection();
- }
-
- void notifyChannelModified(
- String pkgName,
- UserHandle user,
- NotificationChannel channel,
- int modificationType) {
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
- }
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onNotificationChannelModified(pkgName, user, channel, modificationType);
- }
- }
-
- private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
- if (rankingMap == null) {
- return;
- }
- for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
- Ranking ranking = new Ranking();
- if (rankingMap.getRanking(pendingNotification.getKey(), ranking)) {
- pendingNotification.setRanking(ranking);
- }
- }
- }
-
- /**
- * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
- * notifications whose views have not yet been inflated. In general, the system pretends like
- * these don't exist, although there are a couple exceptions.
- */
- public Iterable<NotificationEntry> getPendingNotificationsIterator() {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mPendingNotifications.values();
- }
-
- /**
- * Use this method to retrieve a notification entry that has been prepared for presentation.
- * Note that the notification may be filtered out and never shown to the user.
- *
- * @see #getVisibleNotifications() for the currently sorted and filtered list
- *
- * @return a {@link NotificationEntry} if it has been prepared, else null
- */
- public NotificationEntry getActiveNotificationUnfiltered(String key) {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mActiveNotifications.get(key);
- }
-
- /**
- * Gets the pending or visible notification entry with the given key. Returns null if
- * notification doesn't exist.
- */
- public NotificationEntry getPendingOrActiveNotif(String key) {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- NotificationEntry entry = mPendingNotifications.get(key);
- if (entry != null) {
- return entry;
- }
- return mActiveNotifications.get(key);
- }
-
- private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
- NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
- if (activeExtender != null && activeExtender != extender) {
- activeExtender.setShouldManageLifetime(entry, false);
- }
- mRetainedNotifications.put(entry, extender);
- extender.setShouldManageLifetime(entry, true);
- }
-
- private void cancelLifetimeExtension(NotificationEntry entry) {
- NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
- if (activeExtender != null) {
- activeExtender.setShouldManageLifetime(entry, false);
- }
- }
-
/*
* -----
* Annexed from NotificationData below:
@@ -865,59 +105,11 @@
* we'll try to keep the behavior the same and can simplify these interfaces in another pass
*/
- /** Internalization of NotificationData#remove */
- private void removeVisibleNotification(String key) {
- // no need to synchronize if we're on the main thread dawg
- Assert.isMainThread();
-
- NotificationEntry removed = mActiveNotifications.remove(key);
-
- if (removed == null) return;
- mGroupManager.onEntryRemoved(removed);
- }
-
- /** @return list of active notifications filtered for the current user */
- public List<NotificationEntry> getActiveNotificationsForCurrentUser() {
- Trace.beginSection("NotificationEntryManager.getActiveNotificationsForCurrentUser");
- Assert.isMainThread();
- ArrayList<NotificationEntry> filtered = new ArrayList<>();
-
- final int len = mActiveNotifications.size();
- for (int i = 0; i < len; i++) {
- NotificationEntry entry = mActiveNotifications.valueAt(i);
- if (!mRanker.isNotificationForCurrentProfiles(entry)) {
- continue;
- }
- filtered.add(entry);
- }
- Trace.endSection();
- return filtered;
- }
-
- //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking
- /**
- * @param rankingMap the {@link RankingMap} to apply to the current notification list
- * @param reason the reason for calling this method, which will be logged
- */
- public void updateRanking(RankingMap rankingMap, String reason) {
- Trace.beginSection("NotificationEntryManager.updateRanking");
- updateRankingAndSort(rankingMap, reason);
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingApplied();
- }
- Trace.endSection();
- }
-
/** Resorts / filters the current notification set with the current RankingMap */
public void reapplyFilterAndSort(String reason) {
mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
}
- /** Calls to NotificationRankingManager and updates mSortedAndFiltered */
- private void updateRankingAndSort(RankingMap rankingMap, String reason) {
- mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
- }
-
/** dump the current active notification list. Called from CentralSurfaces */
public void dump(PrintWriter pw, String indent) {
pw.println("NotificationEntryManager (Legacy)");
@@ -955,55 +147,10 @@
pw.println(" notification=" + n.getNotification());
}
- /**
- * This is the answer to the question "what notifications should the user be seeing right now?"
- * These are sorted and filtered, and directly inform the notification shade what to show
- *
- * @return A read-only list of the currently active notifications
- */
- public List<NotificationEntry> getVisibleNotifications() {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mReadOnlyNotifications;
- }
-
- /**
- * Returns a collections containing ALL notifications we know about, including ones that are
- * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
- */
- @NonNull
- @Override
- public Collection<NotificationEntry> getAllNotifs() {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mReadOnlyAllNotifications;
- }
-
- @Nullable
- @Override
- public NotificationEntry getEntry(@NonNull String key) {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return getPendingOrActiveNotif(key);
- }
-
- /** @return A count of the active notifications */
- public int getActiveNotificationsCount() {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mReadOnlyNotifications.size();
- }
-
- /**
- * @return {@code true} if there is at least one notification that should be visible right now
- */
- public boolean hasActiveNotifications() {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- return mReadOnlyNotifications.size() != 0;
- }
-
- @Override
public void addCollectionListener(@NonNull NotifCollectionListener listener) {
mNotifCollectionListeners.add(listener);
}
- @Override
public void removeCollectionListener(@NonNull NotifCollectionListener listener) {
mNotifCollectionListeners.remove(listener);
}
@@ -1024,9 +171,6 @@
boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
}
- private static final String TAG = "NotificationEntryMgr";
- private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
-
/**
* Used when a notification is removed and it doesn't have a reason that maps to one of the
* reasons defined in NotificationListenerService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
deleted file mode 100644
index 54f1380..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
-
-import android.Manifest;
-import android.app.AppGlobals;
-import android.app.Notification;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
-
-import javax.inject.Inject;
-
-/** Component which manages the various reasons a notification might be filtered out.*/
-// TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174.
-// Notification filtering is taken care of across the different Coordinators (mostly
-// KeyguardCoordinator.java)
-@SysUISingleton
-public class NotificationFilter {
-
- private final DebugModeFilterProvider mDebugNotificationFilter;
- private final StatusBarStateController mStatusBarStateController;
- private final KeyguardEnvironment mKeyguardEnvironment;
- private final ForegroundServiceController mForegroundServiceController;
- private final NotificationLockscreenUserManager mUserManager;
- private final Boolean mIsMediaFlagEnabled;
-
- @Inject
- public NotificationFilter(
- DebugModeFilterProvider debugNotificationFilter,
- StatusBarStateController statusBarStateController,
- KeyguardEnvironment keyguardEnvironment,
- ForegroundServiceController foregroundServiceController,
- NotificationLockscreenUserManager userManager,
- MediaFeatureFlag mediaFeatureFlag) {
- mDebugNotificationFilter = debugNotificationFilter;
- mStatusBarStateController = statusBarStateController;
- mKeyguardEnvironment = keyguardEnvironment;
- mForegroundServiceController = foregroundServiceController;
- mUserManager = userManager;
- mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled();
- }
-
- /**
- * @return true if the provided notification should NOT be shown right now.
- */
- public boolean shouldFilterOut(NotificationEntry entry) {
- final StatusBarNotification sbn = entry.getSbn();
- if (mDebugNotificationFilter.shouldFilterOut(entry)) {
- return true;
- }
-
- if (!(mKeyguardEnvironment.isDeviceProvisioned()
- || showNotificationEvenIfUnprovisioned(sbn))) {
- return true;
- }
-
- if (!mKeyguardEnvironment.isNotificationForCurrentProfiles(sbn)) {
- return true;
- }
-
- if (mUserManager.isLockscreenPublicMode(sbn.getUserId())
- && (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
- || mUserManager.shouldHideNotifications(sbn.getUserId())
- || mUserManager.shouldHideNotifications(sbn.getKey()))) {
- return true;
- }
-
- if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
- return true;
- }
-
- if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) {
- return true;
- }
-
- if (entry.getRanking().isSuspended()) {
- return true;
- }
-
- if (mForegroundServiceController.isDisclosureNotification(sbn)
- && !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) {
- // this is a foreground-service disclosure for a user that does not need to show one
- return true;
- }
-
- if (mIsMediaFlagEnabled && isMediaNotification(sbn)) {
- return true;
- }
- return false;
- }
-
- // Q: What kinds of notifications should show during setup?
- // A: Almost none! Only things coming from packages with permission
- // android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
- // as relevant for setup (see below).
- private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
- return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
- }
-
- @VisibleForTesting
- static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
- StatusBarNotification sbn) {
- return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
- sbn.getUid()) == PackageManager.PERMISSION_GRANTED
- && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
- }
-
- private static int checkUidPermission(IPackageManager packageManager, String permission,
- int uid) {
- try {
- return packageManager.checkUidPermission(permission, uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index b6392f7..585d871 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -61,11 +61,11 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
@@ -421,7 +421,7 @@
* Get the children that are actually attached to this notification's row.
*
* TODO: Seems like most callers here should probably be using
- * {@link NotificationGroupManagerLegacy#getChildren}
+ * {@link GroupMembershipManager#getChildren(ListEntry)}
*/
public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
if (row == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
deleted file mode 100644
index a92cff8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection
-
-import android.app.Notification
-import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_MIN
-import android.service.notification.NotificationListenerService.Ranking
-import android.service.notification.NotificationListenerService.RankingMap
-import android.service.notification.StatusBarNotification
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
-import com.android.systemui.statusbar.notification.NotificationFilter
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.PriorityBucket
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import dagger.Lazy
-import java.util.Objects
-import javax.inject.Inject
-
-private const val TAG = "NotifRankingManager"
-
-/**
- * NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and
- * updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters
- * a set of entries (but retains none of them). We also set buckets on the entries here since
- * bucketing is tied closely to sorting.
- *
- * For the curious: this class is one iteration closer to null of what used to be called
- * NotificationData.java.
- */
-open class NotificationRankingManager @Inject constructor(
- private val mediaManagerLazy: Lazy<NotificationMediaManager>,
- private val groupManager: NotificationGroupManagerLegacy,
- private val headsUpManager: HeadsUpManager,
- private val notifFilter: NotificationFilter,
- private val logger: NotificationEntryManagerLogger,
- private val sectionsFeatureManager: NotificationSectionsFeatureManager,
- private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
- private val highPriorityProvider: HighPriorityProvider,
- private val keyguardEnvironment: KeyguardEnvironment
-) : LegacyNotificationRanker {
-
- override var rankingMap: RankingMap? = null
- protected set
- private val mediaManager by lazy {
- mediaManagerLazy.get()
- }
- private val usePeopleFiltering: Boolean
- get() = sectionsFeatureManager.isFilteringEnabled()
- private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b ->
- val na = a.sbn
- val nb = b.sbn
- val aRank = a.ranking.rank
- val bRank = b.ranking.rank
-
- val aIsFsn = a.isColorizedForegroundService()
- val bIsFsn = b.isColorizedForegroundService()
-
- val aCall = a.isImportantCall()
- val bCall = b.isImportantCall()
-
- val aPersonType = a.getPeopleNotificationType()
- val bPersonType = b.getPeopleNotificationType()
-
- val aMedia = a.isImportantMedia()
- val bMedia = b.isImportantMedia()
-
- val aSystemMax = a.isSystemMax()
- val bSystemMax = b.isSystemMax()
-
- val aHeadsUp = a.isRowHeadsUp
- val bHeadsUp = b.isRowHeadsUp
-
- val aIsHighPriority = a.isHighPriority()
- val bIsHighPriority = b.isHighPriority()
- when {
- aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
- // Provide consistent ranking with headsUpManager
- aHeadsUp -> headsUpManager.compare(a, b)
- aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1
- aCall != bCall -> if (aCall) -1 else 1
- usePeopleFiltering && aPersonType != bPersonType ->
- peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
- // Upsort current media notification.
- aMedia != bMedia -> if (aMedia) -1 else 1
- // Upsort PRIORITY_MAX system notifications
- aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
- aIsHighPriority != bIsHighPriority ->
- -1 * aIsHighPriority.compareTo(bIsHighPriority)
- aRank != bRank -> aRank - bRank
- else -> nb.notification.`when`.compareTo(na.notification.`when`)
- }
- }
-
- override fun updateRanking(
- newRankingMap: RankingMap?,
- entries: Collection<NotificationEntry>,
- reason: String
- ): List<NotificationEntry> {
- // TODO: may not be ideal to guard on null here, but this code is implementing exactly what
- // NotificationData used to do
- if (newRankingMap != null) {
- rankingMap = newRankingMap
- updateRankingForEntries(entries)
- }
- return synchronized(this) {
- filterAndSortLocked(entries, reason)
- }
- }
-
- override fun isNotificationForCurrentProfiles(
- entry: NotificationEntry
- ): Boolean {
- return keyguardEnvironment.isNotificationForCurrentProfiles(entry.sbn)
- }
-
- /** Uses the [rankingComparator] to sort notifications which aren't filtered */
- private fun filterAndSortLocked(
- entries: Collection<NotificationEntry>,
- reason: String
- ): List<NotificationEntry> {
- logger.logFilterAndSort(reason)
- val filtered = entries.asSequence()
- .filterNot(this::filter)
- .sortedWith(rankingComparator)
- .toList()
- entries.forEach { it.bucket = getBucketForEntry(it) }
- return filtered
- }
-
- private fun filter(entry: NotificationEntry): Boolean {
- val filtered = notifFilter.shouldFilterOut(entry)
- if (filtered) {
- // notification is removed from the list, so we reset its initialization time
- entry.resetInitializationTime()
- }
- return filtered
- }
-
- @PriorityBucket
- private fun getBucketForEntry(entry: NotificationEntry): Int {
- val isImportantCall = entry.isImportantCall()
- val isHeadsUp = entry.isRowHeadsUp
- val isMedia = entry.isImportantMedia()
- val isSystemMax = entry.isSystemMax()
- return when {
- entry.isColorizedForegroundService() || isImportantCall -> BUCKET_FOREGROUND_SERVICE
- usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE
- isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING
- else -> BUCKET_SILENT
- }
- }
-
- private fun updateRankingForEntries(entries: Iterable<NotificationEntry>) {
- rankingMap?.let { rankingMap ->
- synchronized(entries) {
- for (entry in entries) {
- val newRanking = Ranking()
- if (!rankingMap.getRanking(entry.key, newRanking)) {
- continue
- }
- entry.ranking = newRanking
-
- val newOverrideGroupKey = newRanking.overrideGroupKey
- if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
- val oldGroupKey = entry.sbn.groupKey
- val oldIsGroup = entry.sbn.isGroup
- val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
- entry.sbn.overrideGroupKey = newOverrideGroupKey
- groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
- oldIsGroupSummary)
- }
- }
- }
- }
- }
-
- private fun NotificationEntry.isImportantMedia() =
- key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
-
- private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON
-
- private fun NotificationEntry.getPeopleNotificationType() =
- peopleNotificationIdentifier.getPeopleNotificationType(this)
-
- private fun NotificationEntry.isHighPriority() =
- highPriorityProvider.isHighPriority(this)
-}
-
-// Convenience functions
-private fun NotificationEntry.isSystemMax() =
- importance >= IMPORTANCE_HIGH && sbn.isSystemNotification()
-
-private fun StatusBarNotification.isSystemNotification() =
- "android" == packageName || "com.android.systemui" == packageName
-
-private fun NotificationEntry.isImportantCall() =
- sbn.notification.isStyle(Notification.CallStyle::class.java) && importance > IMPORTANCE_MIN
-
-private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run {
- isForegroundService && isColorized && importance > IMPORTANCE_MIN
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 420f21d..14cc6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -38,6 +38,7 @@
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -126,6 +127,9 @@
private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList);
private final NotifPipelineChoreographer mChoreographer;
+ private int mConsecutiveReentrantRebuilds = 0;
+ @VisibleForTesting public static final int MAX_CONSECUTIVE_REENTRANT_REBUILDS = 3;
+
@Inject
public ShadeListBuilder(
DumpManager dumpManager,
@@ -310,7 +314,7 @@
mLogger.logOnBuildList(reason);
mAllEntries = entries;
- mChoreographer.schedule();
+ scheduleRebuild(/* reentrant = */ false);
}
};
@@ -1332,11 +1336,64 @@
throw new RuntimeException("Missing default sectioner!");
}
- private void rebuildListIfBefore(@PipelineState.StateName int state) {
- mPipelineState.requireIsBefore(state);
- if (mPipelineState.is(STATE_IDLE)) {
- mChoreographer.schedule();
+ private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) {
+ final @PipelineState.StateName int currentState = mPipelineState.getState();
+
+ // If the pipeline is idle, requesting an invalidation is always okay, and starts a new run.
+ if (currentState == STATE_IDLE) {
+ scheduleRebuild(/* reentrant = */ false, rebuildState);
+ return;
}
+
+ // If the pipeline is running, it is okay to request an invalidation of a *later* stage.
+ // Since the current pipeline run hasn't run it yet, no new pipeline run is needed.
+ if (rebuildState > currentState) {
+ return;
+ }
+
+ // If the pipeline is running, it is bad to request an invalidation of *earlier* stages or
+ // the *current* stage; this will run the pipeline more often than needed, and may even
+ // cause an infinite loop of pipeline runs.
+ //
+ // Unfortunately, there are some unfixed bugs that cause reentrant pipeline runs, so we keep
+ // a counter and allow a few reentrant runs in a row between any two non-reentrant runs.
+ //
+ // It is technically possible for a *pair* of invalidations, one reentrant and one not, to
+ // trigger *each other*, alternating responsibility for pipeline runs in an infinite loop
+ // but constantly resetting the reentrant run counter. Hopefully that doesn't happen.
+ scheduleRebuild(/* reentrant = */ true, rebuildState);
+ }
+
+ private void scheduleRebuild(boolean reentrant) {
+ scheduleRebuild(reentrant, STATE_IDLE);
+ }
+
+ private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) {
+ if (!reentrant) {
+ mConsecutiveReentrantRebuilds = 0;
+ mChoreographer.schedule();
+ return;
+ }
+
+ final @PipelineState.StateName int currentState = mPipelineState.getState();
+
+ final String rebuildStateName = PipelineState.getStateName(rebuildState);
+ final String currentStateName = PipelineState.getStateName(currentState);
+ final IllegalStateException exception = new IllegalStateException(
+ "Reentrant notification pipeline rebuild of state " + rebuildStateName
+ + " while pipeline in state " + currentStateName + ".");
+
+ mConsecutiveReentrantRebuilds++;
+
+ if (mConsecutiveReentrantRebuilds > MAX_CONSECUTIVE_REENTRANT_REBUILDS) {
+ Log.e(TAG, "Crashing after more than " + MAX_CONSECUTIVE_REENTRANT_REBUILDS
+ + " consecutive reentrant notification pipeline rebuilds.", exception);
+ throw exception;
+ }
+
+ Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds
+ + " consecutive reentrant notification pipeline rebuild(s).", exception);
+ mChoreographer.schedule();
}
private static int countChildren(List<ListEntry> entries) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index ef1e57b..6e76691 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -286,7 +286,7 @@
if (isInflated(child)) {
// TODO: May want to put an animation hint here so view manager knows to treat
// this differently from a regular removal animation
- freeNotifViews(child);
+ freeNotifViews(child, "Past last visible group child");
}
}
}
@@ -379,7 +379,8 @@
mNotifInflatingFilter.invalidateList("onInflationFinished for " + logKey(entry));
}
- private void freeNotifViews(NotificationEntry entry) {
+ private void freeNotifViews(NotificationEntry entry, String reason) {
+ mLogger.logFreeNotifViews(entry, reason);
mViewBarn.removeViewForEntry(entry);
mNotifInflater.releaseViews(entry);
// TODO: clear the entry's row here, or even better, stop setting the row on the entry!
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 30f1315..c4f4ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -31,7 +31,7 @@
buffer.log(TAG, LogLevel.DEBUG, {
str1 = entry.logKey
}, {
- "NOTIF INFLATED $str1"
+ "Inflation completed for notif $str1"
})
}
@@ -40,7 +40,16 @@
str1 = entry.logKey
str2 = reason
}, {
- "NOTIF INFLATION ABORTED $str1 reason=$str2"
+ "Infation aborted for notif $str1 reason=$str2"
+ })
+ }
+
+ fun logFreeNotifViews(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "Freeing content views for notif $str1 reason=$str2"
})
}
@@ -70,4 +79,4 @@
}
}
-private const val TAG = "PreparationCoordinator"
\ No newline at end of file
+private const val TAG = "PreparationCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
index 9d5b859..496266f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.notification.collection.inflation
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.NotificationEntryListener
-import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener
import javax.inject.Inject
@@ -31,12 +29,4 @@
/** Emit the [Listener.onViewBound] event to all registered listeners. */
fun notifyViewBound(entry: NotificationEntry) =
listeners.forEach { listener -> listener.onViewBound(entry) }
-
- /** Initialize this for the legacy pipeline. */
- fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) {
- notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
- override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry)
- override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry)
- })
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt
deleted file mode 100644
index 49bc48e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRanker.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy
-
-import android.service.notification.NotificationListenerService
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-
-interface LegacyNotificationRanker {
- val rankingMap: NotificationListenerService.RankingMap?
-
- fun updateRanking(
- newRankingMap: NotificationListenerService.RankingMap?,
- entries: Collection<NotificationEntry>,
- reason: String
- ): List<NotificationEntry>
-
- fun isNotificationForCurrentProfiles(
- entry: NotificationEntry
- ): Boolean
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java
deleted file mode 100644
index 12353f8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationRankerStub.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Stub implementation that we use until we get passed the "real" one in the form of
- * {@link com.android.systemui.statusbar.notification.collection.NotificationRankingManager}
- */
-public class LegacyNotificationRankerStub implements LegacyNotificationRanker {
- private RankingMap mRankingMap = new RankingMap(new Ranking[] {});
-
- @NonNull
- @Override
- public List<NotificationEntry> updateRanking(
- @Nullable RankingMap newRankingMap,
- @NonNull Collection<NotificationEntry> entries,
- @NonNull String reason) {
- if (newRankingMap != null) {
- mRankingMap = newRankingMap;
- }
- List<NotificationEntry> ranked = new ArrayList<>(entries);
- ranked.sort(mEntryComparator);
- return ranked;
- }
-
- @Nullable
- @Override
- public RankingMap getRankingMap() {
- return mRankingMap;
- }
-
- private final Comparator<NotificationEntry> mEntryComparator = Comparator.comparingLong(
- o -> o.getSbn().getNotification().when);
-
- @Override
- public boolean isNotificationForCurrentProfiles(@NonNull NotificationEntry entry) {
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java
index ae4f2bb..89445a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java
@@ -19,9 +19,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import javax.inject.Inject;
@@ -32,44 +29,17 @@
@SysUISingleton
public class LowPriorityInflationHelper {
private final NotificationGroupManagerLegacy mGroupManager;
- private final RowContentBindStage mRowContentBindStage;
private final NotifPipelineFlags mNotifPipelineFlags;
@Inject
LowPriorityInflationHelper(
NotificationGroupManagerLegacy groupManager,
- RowContentBindStage rowContentBindStage,
NotifPipelineFlags notifPipelineFlags) {
mGroupManager = groupManager;
- mRowContentBindStage = rowContentBindStage;
mNotifPipelineFlags = notifPipelineFlags;
}
/**
- * Check if we inflated the wrong version of the view and if we need to reinflate the
- * content views to be their low priority version or not.
- *
- * Whether we inflate the low priority view or not depends on the notification being visually
- * part of a group. Since group membership is determined AFTER inflation, we're forced to check
- * again at a later point in the pipeline to see if we inflated the wrong view and reinflate
- * the correct one here.
- *
- * TODO: The group manager should run before inflation so that we don't deal with this
- */
- public void recheckLowPriorityViewAndInflate(
- NotificationEntry entry,
- ExpandableNotificationRow row) {
- mNotifPipelineFlags.checkLegacyPipelineEnabled();
- RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
- final boolean shouldBeLowPriority = shouldUseLowPriorityView(entry);
- if (!row.isRemoved() && row.isLowPriority() != shouldBeLowPriority) {
- params.setUseLowPriority(shouldBeLowPriority);
- mRowContentBindStage.requestRebind(entry,
- en -> row.setIsLowPriority(shouldBeLowPriority));
- }
- }
-
- /**
* Whether the notification should inflate a low priority version of its content views.
*/
public boolean shouldUseLowPriorityView(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index b8da9a8..d41f6fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -33,13 +33,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.Compile;
import com.android.wm.shell.bubbles.Bubbles;
@@ -47,7 +43,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -65,12 +60,7 @@
* 2. Tracking group expansion states
*/
@SysUISingleton
-public class NotificationGroupManagerLegacy implements
- OnHeadsUpChangedListener,
- StateListener,
- GroupMembershipManager,
- GroupExpansionManager,
- Dumpable {
+public class NotificationGroupManagerLegacy implements StateListener, Dumpable {
private static final String TAG = "LegacyNotifGroupManager";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
@@ -81,14 +71,10 @@
*/
private static final long POST_BATCH_MAX_AGE = 5000;
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
- private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
- new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private final Optional<Bubbles> mBubblesOptional;
private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get);
- private int mBarState = -1;
- private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
- private HeadsUpManager mHeadsUpManager;
+ private final HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private boolean mIsUpdatingUnchangedGroup;
@Inject
@@ -111,47 +97,8 @@
mEventDispatcher.registerGroupChangeListener(listener);
}
- @Override
- public void registerGroupExpansionChangeListener(OnGroupExpansionChangeListener listener) {
- mExpansionChangeListeners.add(listener);
- }
-
- @Override
- public boolean isGroupExpanded(NotificationEntry entry) {
- NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
- if (group == null) {
- return false;
- }
- return group.expanded;
- }
-
- /**
- * @return if the group that this notification is associated with logically is expanded
- */
- public boolean isLogicalGroupExpanded(StatusBarNotification sbn) {
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
- if (group == null) {
- return false;
- }
- return group.expanded;
- }
-
- @Override
- public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
- NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
- if (group == null) {
- return;
- }
- setGroupExpanded(group, expanded);
- }
-
private void setGroupExpanded(NotificationGroup group, boolean expanded) {
group.expanded = expanded;
- if (group.summary != null) {
- for (OnGroupExpansionChangeListener listener : mExpansionChangeListeners) {
- listener.onGroupExpansionChange(group.summary.getRow(), expanded);
- }
- }
}
/**
@@ -212,19 +159,6 @@
}
}
- /**
- * Notify the group manager that a new entry was added
- */
- public void onEntryAdded(final NotificationEntry added) {
- if (SPEW) {
- Log.d(TAG, "onEntryAdded: entry=" + logKey(added));
- }
- mEventDispatcher.openBufferScope();
- updateIsolation(added);
- onEntryAddedInternal(added);
- mEventDispatcher.closeBufferScope();
- }
-
private void onEntryAddedInternal(final NotificationEntry added) {
if (added.isRowRemoved()) {
added.setDebugThrowable(new Throwable());
@@ -499,106 +433,13 @@
return result;
}
- /**
- * Update an entry's group information
- * @param entry notification entry to update
- * @param oldNotification previous notification info before this update
- */
- public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
- if (SPEW) {
- Log.d(TAG, "onEntryUpdated: entry=" + logKey(entry));
- }
- onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
- oldNotification.getNotification().isGroupSummary());
- }
-
- /**
- * Updates an entry's group information
- * @param entry notification entry to update
- * @param oldGroupKey the notification's previous group key before this update
- * @param oldIsGroup whether this notification was a group before this update
- * @param oldIsGroupSummary whether this notification was a group summary before this update
- */
- public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
- boolean oldIsGroupSummary) {
- String newGroupKey = entry.getSbn().getGroupKey();
- boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
- boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
- boolean isGroupChild = isGroupChild(entry.getSbn());
- mEventDispatcher.openBufferScope();
- mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
- if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
- onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
- }
- onEntryAddedInternal(entry);
- mIsUpdatingUnchangedGroup = false;
- if (isIsolated(entry.getSbn().getKey())) {
- mIsolatedEntries.put(entry.getKey(), entry.getSbn());
- if (groupKeysChanged) {
- updateSuppression(mGroupMap.get(oldGroupKey));
- }
- // Always update the suppression of the group from which you're isolated, in case
- // this entry was or now is the alertOverride for that group.
- updateSuppression(mGroupMap.get(newGroupKey));
- } else if (!wasGroupChild && isGroupChild) {
- onEntryBecomingChild(entry);
- }
- mEventDispatcher.closeBufferScope();
- }
-
- /**
- * Whether the given notification is the summary of a group that is being suppressed
- */
- public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
- return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
- }
-
- /**
- * If the given notification is a summary, get the group for it.
- */
- public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
- if (sbn.getNotification().isGroupSummary()) {
- return mGroupMap.get(getGroupKey(sbn));
- }
- return null;
- }
-
- private boolean isOnlyChild(StatusBarNotification sbn) {
- return !sbn.getNotification().isGroupSummary()
- && getTotalNumberOfChildren(sbn) == 1;
- }
-
- @Override
- public boolean isOnlyChildInGroup(NotificationEntry entry) {
- final StatusBarNotification sbn = entry.getSbn();
- if (!isOnlyChild(sbn)) {
- return false;
- }
- NotificationEntry logicalGroupSummary = getLogicalGroupSummary(entry);
- return logicalGroupSummary != null && !logicalGroupSummary.getSbn().equals(sbn);
- }
-
- private int getTotalNumberOfChildren(StatusBarNotification sbn) {
- int isolatedChildren = getNumberOfIsolatedChildren(sbn.getGroupKey());
- NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
- int realChildren = group != null ? group.children.size() : 0;
- return isolatedChildren + realChildren;
- }
-
- private boolean isGroupSuppressed(String groupKey) {
- NotificationGroup group = mGroupMap.get(groupKey);
- return group != null && group.suppressed;
- }
-
private void setStatusBarState(int newState) {
- mBarState = newState;
- if (mBarState == StatusBarState.KEYGUARD) {
+ if (newState == StatusBarState.KEYGUARD) {
collapseGroups();
}
}
- @Override
- public void collapseGroups() {
+ private void collapseGroups() {
// Because notifications can become isolated when the group becomes suppressed it can
// lead to concurrent modifications while looping. We need to make a copy.
ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
@@ -612,7 +453,6 @@
}
}
- @Override
public boolean isChildInGroup(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
if (!isGroupChild(sbn)) {
@@ -631,67 +471,6 @@
return true;
}
- @Override
- public boolean isGroupSummary(NotificationEntry entry) {
- final StatusBarNotification sbn = entry.getSbn();
- if (!isGroupSummary(sbn)) {
- return false;
- }
- NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
- if (group == null || group.summary == null) {
- return false;
- }
- return !group.children.isEmpty() && Objects.equals(group.summary.getSbn(), sbn);
- }
-
- @Override
- public NotificationEntry getGroupSummary(NotificationEntry entry) {
- return getGroupSummary(getGroupKey(entry.getSbn()));
- }
-
- @Override
- public NotificationEntry getLogicalGroupSummary(NotificationEntry entry) {
- return getGroupSummary(entry.getSbn().getGroupKey());
- }
-
- @Nullable
- private NotificationEntry getGroupSummary(String groupKey) {
- NotificationGroup group = mGroupMap.get(groupKey);
- //TODO: see if this can become an Entry
- return group == null ? null
- : group.summary;
- }
-
- /**
- * Get the children that are logically in the summary's group, whether or not they are isolated.
- *
- * @param summary summary of a group
- * @return list of the children
- */
- public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) {
- NotificationGroup group = mGroupMap.get(summary.getGroupKey());
- if (group == null) {
- return null;
- }
- ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
- for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(summary.getGroupKey())) {
- children.add(mGroupMap.get(sbn.getKey()).summary);
- }
- }
- return children;
- }
-
- @Override
- public @Nullable List<NotificationEntry> getChildren(ListEntry listEntrySummary) {
- NotificationEntry summary = listEntrySummary.getRepresentativeEntry();
- NotificationGroup group = mGroupMap.get(summary.getSbn().getGroupKey());
- if (group == null) {
- return null;
- }
- return new ArrayList<>(group.children.values());
- }
-
/**
* If there is a {@link NotificationGroup} associated with the provided entry, this method
* will update the suppression of that group.
@@ -710,7 +489,7 @@
* @param sbn notification to check
* @return the key of the notification
*/
- public String getGroupKey(StatusBarNotification sbn) {
+ private String getGroupKey(StatusBarNotification sbn) {
return getGroupKey(sbn.getKey(), sbn.getGroupKey());
}
@@ -721,37 +500,17 @@
return groupKey;
}
- @Override
- public boolean toggleGroupExpansion(NotificationEntry entry) {
- NotificationGroup group = mGroupMap.get(getGroupKey(entry.getSbn()));
- if (group == null) {
- return false;
- }
- setGroupExpanded(group, !group.expanded);
- return group.expanded;
- }
-
private boolean isIsolated(String sbnKey) {
return mIsolatedEntries.containsKey(sbnKey);
}
/**
- * Is this notification the summary of a group?
- */
- public boolean isGroupSummary(StatusBarNotification sbn) {
- if (isIsolated(sbn.getKey())) {
- return true;
- }
- return sbn.getNotification().isGroupSummary();
- }
-
- /**
* Whether a notification is visually a group child.
*
* @param sbn notification to check
* @return true if it is visually a group child
*/
- public boolean isGroupChild(StatusBarNotification sbn) {
+ private boolean isGroupChild(StatusBarNotification sbn) {
return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
}
@@ -762,11 +521,6 @@
return isGroup && !isGroupSummary;
}
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- updateIsolation(entry);
- }
-
/**
* Whether a notification that is normally part of a group should be temporarily isolated from
* the group and put in their own group visually. This generally happens when the notification
@@ -783,9 +537,6 @@
if (isImportantConversation(entry)) {
return true;
}
- if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
- return false;
- }
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
return (sbn.getNotification().fullScreenIntent != null
|| notificationGroup == null
@@ -825,7 +576,7 @@
/**
* Update the isolation of an entry, splitting it from the group.
*/
- public void updateIsolation(NotificationEntry entry) {
+ private void updateIsolation(NotificationEntry entry) {
// We need to buffer a few events because we do isolation changes in 3 steps:
// removeInternal, update mIsolatedEntries, addInternal. This means that often the
// alertOverride will update on the removal, however processing the event in that case can
@@ -866,13 +617,6 @@
|| notificationGroup.summary.isGroupNotFullyVisible();
}
- /**
- * Directly set the heads up manager to avoid circular dependencies
- */
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("GroupManagerLegacy state:");
@@ -893,7 +637,7 @@
}
/** Get the group key, reformatted for logging, for the (optional) group */
- public static String logGroupKey(NotificationGroup group) {
+ private static String logGroupKey(NotificationGroup group) {
if (group == null) {
return "null";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
index 456bf51..bb8c0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
@@ -16,84 +16,32 @@
package com.android.systemui.statusbar.notification.collection.legacy;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.View;
-
-import androidx.collection.ArraySet;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* A manager that ensures that notifications are visually stable. It will suppress reorderings
* and reorder at the right time when they are out of view.
*/
-public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpable {
+public class VisualStabilityManager {
- private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
-
- private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
- private final ArraySet<Callback> mPersistentReorderingCallbacks = new ArraySet<>();
- private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
- private final ArraySet<Callback> mPersistentGroupCallbacks = new ArraySet<>();
- private final Handler mHandler;
private final VisualStabilityProvider mVisualStabilityProvider;
private boolean mPanelExpanded;
private boolean mScreenOn;
- private boolean mReorderingAllowed;
- private boolean mGroupChangedAllowed;
- private boolean mIsTemporaryReorderingAllowed;
- private long mTemporaryReorderingStart;
- private VisibilityLocationProvider mVisibilityLocationProvider;
- private ArraySet<View> mAllowedReorderViews = new ArraySet<>();
- private ArraySet<NotificationEntry> mLowPriorityReorderingViews = new ArraySet<>();
- private ArraySet<View> mAddedChildren = new ArraySet<>();
private boolean mPulsing;
/**
* Injected constructor. See {@link NotificationsModule}.
*/
public VisualStabilityManager(
- NotificationEntryManager notificationEntryManager,
VisualStabilityProvider visualStabilityProvider,
- @Main Handler handler,
StatusBarStateController statusBarStateController,
- WakefulnessLifecycle wakefulnessLifecycle,
- DumpManager dumpManager) {
+ WakefulnessLifecycle wakefulnessLifecycle) {
mVisualStabilityProvider = visualStabilityProvider;
- mHandler = handler;
- dumpManager.registerDumpable(this);
-
- if (notificationEntryManager != null) {
- notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- final boolean ambientStateHasChanged =
- entry.isAmbient() != entry.getRow().isLowPriority();
- if (ambientStateHasChanged) {
- // note: entries are removed in onReorderingFinished
- mLowPriorityReorderingViews.add(entry);
- }
- }
- });
- }
if (statusBarStateController != null) {
setPulsing(statusBarStateController.isPulsing());
@@ -116,40 +64,6 @@
}
/**
- * Add a callback to invoke when reordering is allowed again.
- *
- * @param callback the callback to add
- * @param persistent {@code true} if this callback should this callback be persisted, otherwise
- * it will be removed after a single invocation
- */
- public void addReorderingAllowedCallback(Callback callback, boolean persistent) {
- if (persistent) {
- mPersistentReorderingCallbacks.add(callback);
- }
- if (mReorderingAllowedCallbacks.contains(callback)) {
- return;
- }
- mReorderingAllowedCallbacks.add(callback);
- }
-
- /**
- * Add a callback to invoke when group changes are allowed again.
- *
- * @param callback the callback to add
- * @param persistent {@code true} if this callback should this callback be persisted, otherwise
- * it will be removed after a single invocation
- */
- public void addGroupChangesAllowedCallback(Callback callback, boolean persistent) {
- if (persistent) {
- mPersistentGroupCallbacks.add(callback);
- }
- if (mGroupChangesAllowedCallbacks.contains(callback)) {
- return;
- }
- mGroupChangesAllowedCallbacks.add(callback);
- }
-
- /**
* @param screenOn whether the screen is on
*/
private void setScreenOn(boolean screenOn) {
@@ -177,133 +91,8 @@
}
private void updateAllowedStates() {
- boolean reorderingAllowed =
- (!mScreenOn || !mPanelExpanded || mIsTemporaryReorderingAllowed) && !mPulsing;
- boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
- mReorderingAllowed = reorderingAllowed;
- if (changedToTrue) {
- notifyChangeAllowed(mReorderingAllowedCallbacks, mPersistentReorderingCallbacks);
- }
+ boolean reorderingAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
mVisualStabilityProvider.setReorderingAllowed(reorderingAllowed);
- boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
- changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
- mGroupChangedAllowed = groupChangesAllowed;
- if (changedToTrue) {
- notifyChangeAllowed(mGroupChangesAllowedCallbacks, mPersistentGroupCallbacks);
- }
- }
-
- private void notifyChangeAllowed(ArrayList<Callback> callbacks,
- ArraySet<Callback> persistentCallbacks) {
- for (int i = 0; i < callbacks.size(); i++) {
- Callback callback = callbacks.get(i);
- callback.onChangeAllowed();
- if (!persistentCallbacks.contains(callback)) {
- callbacks.remove(callback);
- i--;
- }
- }
- }
-
- /**
- * @return whether reordering is currently allowed in general.
- */
- public boolean isReorderingAllowed() {
- return mReorderingAllowed;
- }
-
- /**
- * @return whether changes in the grouping should be allowed right now.
- */
- public boolean areGroupChangesAllowed() {
- return mGroupChangedAllowed;
- }
-
- /**
- * @return whether a specific notification is allowed to reorder. Certain notifications are
- * allowed to reorder even if {@link #isReorderingAllowed()} returns false, like newly added
- * notifications or heads-up notifications that are out of view.
- */
- public boolean canReorderNotification(ExpandableNotificationRow row) {
- if (mReorderingAllowed) {
- return true;
- }
- if (mAddedChildren.contains(row)) {
- return true;
- }
- if (mLowPriorityReorderingViews.contains(row.getEntry())) {
- return true;
- }
- if (mAllowedReorderViews.contains(row)
- && !mVisibilityLocationProvider.isInVisibleLocation(row.getEntry())) {
- return true;
- }
- return false;
- }
-
- public void setVisibilityLocationProvider(
- VisibilityLocationProvider visibilityLocationProvider) {
- mVisibilityLocationProvider = visibilityLocationProvider;
- }
-
- /**
- * Notifications have been reordered, so reset all the allowed list of views that are allowed
- * to reorder.
- */
- public void onReorderingFinished() {
- mAllowedReorderViews.clear();
- mAddedChildren.clear();
- mLowPriorityReorderingViews.clear();
- }
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- if (isHeadsUp) {
- // Heads up notifications should in general be allowed to reorder if they are out of
- // view and stay at the current location if they aren't.
- mAllowedReorderViews.add(entry.getRow());
- }
- }
-
- /**
- * Temporarily allows reordering of the entire shade for a period of 1000ms. Subsequent calls
- * to this method will extend the timer.
- */
- public void temporarilyAllowReordering() {
- mHandler.removeCallbacks(mOnTemporaryReorderingExpired);
- mHandler.postDelayed(mOnTemporaryReorderingExpired, TEMPORARY_REORDERING_ALLOWED_DURATION);
- if (!mIsTemporaryReorderingAllowed) {
- mTemporaryReorderingStart = SystemClock.elapsedRealtime();
- }
- mIsTemporaryReorderingAllowed = true;
- updateAllowedStates();
- }
-
- private final Runnable mOnTemporaryReorderingExpired = () -> {
- mIsTemporaryReorderingAllowed = false;
- updateAllowedStates();
- };
-
- /**
- * Notify the visual stability manager that a new view was added and should be allowed to
- * reorder next time.
- */
- public void notifyViewAddition(View view) {
- mAddedChildren.add(view);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("VisualStabilityManager state:");
- pw.print(" mIsTemporaryReorderingAllowed="); pw.println(mIsTemporaryReorderingAllowed);
- pw.print(" mTemporaryReorderingStart="); pw.println(mTemporaryReorderingStart);
-
- long now = SystemClock.elapsedRealtime();
- pw.print(" Temporary reordering window has been open for ");
- pw.print(now - (mIsTemporaryReorderingAllowed ? mTemporaryReorderingStart : now));
- pw.println("ms");
-
- pw.println();
}
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index fac234c..eda2eec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -30,7 +30,6 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -53,7 +52,6 @@
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager;
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -123,22 +121,13 @@
NotificationEntryManagerLogger logger,
NotificationGroupManagerLegacy groupManager,
NotifPipelineFlags notifPipelineFlags,
- Lazy<NotificationRowBinder> notificationRowBinderLazy,
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- DumpManager dumpManager,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
- logger,
- groupManager,
- notifPipelineFlags,
- notificationRowBinderLazy,
- notificationRemoteInputManagerLazy,
- leakDetector,
- statusBarService,
- dumpManager,
- bgExecutor);
+ logger
+ );
}
/** Provides an instance of {@link NotificationGutsManager} */
@@ -162,8 +151,7 @@
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController,
- DumpManager dumpManager) {
+ ShadeController shadeController) {
return new NotificationGutsManager(
context,
centralSurfacesOptionalLazy,
@@ -181,8 +169,8 @@
bubblesManagerOptional,
uiEventLogger,
onUserInteractionCallback,
- shadeController,
- dumpManager);
+ shadeController
+ );
}
/** Provides an instance of {@link NotifGutsViewManager} */
@@ -193,19 +181,14 @@
@SysUISingleton
@Provides
static VisualStabilityManager provideVisualStabilityManager(
- NotificationEntryManager notificationEntryManager,
VisualStabilityProvider visualStabilityProvider,
- @Main Handler handler,
StatusBarStateController statusBarStateController,
- WakefulnessLifecycle wakefulnessLifecycle,
- DumpManager dumpManager) {
+ WakefulnessLifecycle wakefulnessLifecycle) {
return new VisualStabilityManager(
- notificationEntryManager,
visualStabilityProvider,
- handler,
statusBarStateController,
- wakefulnessLifecycle,
- dumpManager);
+ wakefulnessLifecycle
+ );
}
/** Provides an instance of {@link NotificationLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
deleted file mode 100644
index 74fb3f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.interruption;
-
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import javax.inject.Inject;
-
-/**
- * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
- * entry events and appropriately binds or unbinds the heads up view and promotes it to the top
- * of the screen.
- */
-@SysUISingleton
-public class HeadsUpController {
- private final HeadsUpViewBinder mHeadsUpViewBinder;
- private final NotificationInterruptStateProvider mInterruptStateProvider;
- private final NotificationRemoteInputManager mRemoteInputManager;
- private final VisualStabilityManager mVisualStabilityManager;
- private final StatusBarStateController mStatusBarStateController;
- private final NotificationListener mNotificationListener;
- private final HeadsUpManager mHeadsUpManager;
-
- @Inject
- HeadsUpController(
- HeadsUpViewBinder headsUpViewBinder,
- NotificationInterruptStateProvider notificationInterruptStateProvider,
- HeadsUpManager headsUpManager,
- NotificationRemoteInputManager remoteInputManager,
- StatusBarStateController statusBarStateController,
- VisualStabilityManager visualStabilityManager,
- NotificationListener notificationListener) {
- mHeadsUpViewBinder = headsUpViewBinder;
- mHeadsUpManager = headsUpManager;
- mInterruptStateProvider = notificationInterruptStateProvider;
- mRemoteInputManager = remoteInputManager;
- mStatusBarStateController = statusBarStateController;
- mVisualStabilityManager = visualStabilityManager;
- mNotificationListener = notificationListener;
- }
-
- /**
- * Attach this controller and add its listeners.
- */
- public void attach(
- NotificationEntryManager entryManager,
- HeadsUpManager headsUpManager) {
- entryManager.addCollectionListener(mCollectionListener);
- headsUpManager.addListener(mOnHeadsUpChangedListener);
- }
-
- private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- if (mInterruptStateProvider.shouldHeadsUp(entry)) {
- mHeadsUpViewBinder.bindHeadsUpView(
- entry, HeadsUpController.this::showAlertingView);
- }
- }
-
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- updateHunState(entry);
- }
-
- @Override
- public void onEntryRemoved(NotificationEntry entry, int reason) {
- stopAlerting(entry);
- }
-
- @Override
- public void onEntryCleanUp(NotificationEntry entry) {
- mHeadsUpViewBinder.abortBindCallback(entry);
- }
- };
-
- /**
- * Adds the entry to the HUN manager and show it for the first time.
- */
- private void showAlertingView(NotificationEntry entry) {
- mHeadsUpManager.showNotification(entry);
- if (!mStatusBarStateController.isDozing()) {
- // Mark as seen immediately
- setNotificationShown(entry.getSbn());
- }
- }
-
- private void updateHunState(NotificationEntry entry) {
- boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
- // includes check for whether this notification should be filtered:
- boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry);
- final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
- if (wasHeadsUp) {
- if (shouldHeadsUp) {
- mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
- } else {
- // We don't want this to be interrupting anymore, let's remove it
- mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
- }
- } else if (shouldHeadsUp && hunAgain) {
- mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification);
- }
- }
-
- private void setNotificationShown(StatusBarNotification n) {
- try {
- mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
- } catch (RuntimeException e) {
- Log.d(TAG, "failed setNotificationsShown: ", e);
- }
- }
-
- private void stopAlerting(NotificationEntry entry) {
- // Attempt to remove notifications from their HUN manager.
- // Though the remove itself may fail, it lets the manager know to remove as soon as
- // possible.
- String key = entry.getKey();
- if (mHeadsUpManager.isAlerting(key)) {
- // A cancel() in response to a remote input shouldn't be delayed, as it makes the
- // sending look longer than it takes.
- // Also we should not defer the removal if reordering isn't allowed since otherwise
- // some notifications can't disappear before the panel is closed.
- boolean ignoreEarliestRemovalTime =
- mRemoteInputManager.isSpinning(key)
- && !FORCE_REMOTE_INPUT_HISTORY
- || !mVisualStabilityManager.isReorderingAllowed();
- mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
- }
- }
-
- /**
- * Checks whether an update for a notification warrants an alert for the user.
- *
- * @param oldEntry the entry for this notification.
- * @param newNotification the new notification for this entry.
- * @return whether this notification should alert the user.
- */
- public static boolean alertAgain(
- NotificationEntry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
- private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
- @Override
- public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
- if (!isHeadsUp && !entry.getRow().isRemoved()) {
- mHeadsUpViewBinder.unbindHeadsUpView(entry);
- }
- }
- };
-
- private static final String TAG = "HeadsUpBindController";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 5ef2b9e..6f41425 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -41,8 +41,7 @@
* figuring out the right heads up inflation parameters and inflating/freeing the heads up
* content view.
*
- * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
- * (i.e. when {@link HeadsUpController} is removed).
+ * TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated.
*/
@SysUISingleton
public class HeadsUpViewBinder {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c01802..9ad906c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -94,7 +94,6 @@
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
@@ -904,21 +903,6 @@
return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
}
- /**
- * Apply the order given in the list to the children.
- *
- * @param childOrder the new list order
- * @param visualStabilityManager
- * @param callback the callback to invoked in case it is not allowed
- * @return whether the list order has changed
- */
- public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
- VisualStabilityManager visualStabilityManager,
- VisualStabilityManager.Callback callback) {
- return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
- visualStabilityManager, callback);
- }
-
/** Updates states of all children. */
public void updateChildrenStates(AmbientState ambientState) {
if (mIsSummaryWithChildren) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index c4ff259..7b0b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -55,7 +55,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
@@ -81,8 +80,7 @@
* Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
* closing guts, and keeping track of the currently exposed notification guts.
*/
-public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
- NotifGutsViewManager {
+public class NotificationGutsManager implements NotifGutsViewManager {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -107,13 +105,10 @@
// which notification is currently being longpress-examined by the user
private NotificationGuts mNotificationGutsExposed;
private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
- private NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
private NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationListContainer mListContainer;
private OnSettingsClickListener mOnSettingsClickListener;
- @VisibleForTesting
- protected String mKeyToRemoveOnGutsClosed;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final Handler mMainHandler;
@@ -148,8 +143,7 @@
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController,
- DumpManager dumpManager) {
+ ShadeController shadeController) {
mContext = context;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mMainHandler = mainHandler;
@@ -167,8 +161,6 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
-
- dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -266,12 +258,6 @@
mGutsListener.onGutsClose(entry);
}
String key = entry.getKey();
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
- if (mNotificationLifetimeFinishedCallback != null) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
- }
- }
});
View gutsView = item.getGutsView();
@@ -658,46 +644,6 @@
return true;
}
- @Override
- public void setCallback(NotificationSafeToRemoveCallback callback) {
- mNotificationLifetimeFinishedCallback = callback;
- }
-
- @Override
- public boolean shouldExtendLifetime(NotificationEntry entry) {
- return entry != null
- &&(mNotificationGutsExposed != null
- && entry.getGuts() != null
- && mNotificationGutsExposed == entry.getGuts()
- && !mNotificationGutsExposed.isLeavebehind());
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
- if (shouldExtend) {
- mKeyToRemoveOnGutsClosed = entry.getKey();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification because it's showing guts. " + entry.getKey());
- }
- } else {
- if (mKeyToRemoveOnGutsClosed != null
- && mKeyToRemoveOnGutsClosed.equals(entry.getKey())) {
- mKeyToRemoveOnGutsClosed = null;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Notification that was kept for guts was updated. "
- + entry.getKey());
- }
- }
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("NotificationGutsManager state:");
- pw.print(" mKeyToRemoveOnGutsClosed (legacy): ");
- pw.println(mKeyToRemoveOnGutsClosed);
- }
-
/**
* @param gutsListener the listener for open and close guts events
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a76f0827..d77e03f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -43,7 +43,6 @@
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
@@ -462,39 +461,6 @@
return mAttachedChildren;
}
- /**
- * Apply the order given in the list to the children.
- *
- * @param childOrder the new list order
- * @param visualStabilityManager
- * @param callback
- * @return whether the list order has changed
- */
- public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
- VisualStabilityManager visualStabilityManager,
- VisualStabilityManager.Callback callback) {
- if (childOrder == null) {
- return false;
- }
- boolean result = false;
- for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
- ExpandableNotificationRow child = mAttachedChildren.get(i);
- ExpandableNotificationRow desiredChild = childOrder.get(i);
- if (child != desiredChild) {
- if (visualStabilityManager.canReorderNotification(desiredChild)) {
- mAttachedChildren.remove(desiredChild);
- mAttachedChildren.add(i, desiredChild);
- result = true;
- } else {
- visualStabilityManager.addReorderingAllowedCallback(callback,
- false /* persistent */);
- }
- }
- }
- updateExpansionStates();
- return result;
- }
-
/** To be called any time the rows have been updated */
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 54e26c3..91a2813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -20,9 +20,6 @@
import android.view.View
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.media.KeyguardMediaController
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
@@ -33,32 +30,20 @@
import com.android.systemui.statusbar.notification.dagger.SilentHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.children
import com.android.systemui.util.foldToSparseArray
-import com.android.systemui.util.takeUntil
-import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
- * Manages the boundaries of the notification sections (incoming, conversations, high priority, and
- * low priority).
- *
- * In the legacy notification pipeline, this is responsible for correctly positioning all section
- * headers after the [NotificationStackScrollLayout] has had notifications added/removed/changed. In
- * the new pipeline, this is handled as part of the [ShadeViewManager].
+ * Manages section headers in the NSSL.
*
* TODO: Move remaining sections logic from NSSL into this class.
*/
class NotificationSectionsManager @Inject internal constructor(
- private val statusBarStateController: StatusBarStateController,
private val configurationController: ConfigurationController,
private val keyguardMediaController: KeyguardMediaController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
- private val logger: NotificationSectionsLogger,
- private val notifPipelineFlags: NotifPipelineFlags,
private val mediaContainerController: MediaContainerController,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@PeopleHeader private val peopleHeaderController: SectionHeaderController,
@@ -139,205 +124,6 @@
else -> null
}
- private fun logShadeChild(i: Int, child: View) {
- when {
- child === incomingHeaderView -> logger.logIncomingHeader(i)
- child === mediaControlsView -> logger.logMediaControls(i)
- child === peopleHeaderView -> logger.logConversationsHeader(i)
- child === alertingHeaderView -> logger.logAlertingHeader(i)
- child === silentHeaderView -> logger.logSilentHeader(i)
- child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass)
- else -> {
- val isHeadsUp = child.isHeadsUp
- when (child.entry.bucket) {
- BUCKET_HEADS_UP -> logger.logHeadsUp(i, isHeadsUp)
- BUCKET_PEOPLE -> logger.logConversation(i, isHeadsUp)
- BUCKET_ALERTING -> logger.logAlerting(i, isHeadsUp)
- BUCKET_SILENT -> logger.logSilent(i, isHeadsUp)
- }
- }
- }
- }
- private fun logShadeContents() = traceSection("NotifSectionsManager.logShadeContents") {
- parent.children.forEachIndexed(::logShadeChild)
- }
-
- private val isUsingMultipleSections: Boolean
- get() = sectionsFeatureManager.getNumberOfBuckets() > 1
-
- @VisibleForTesting
- fun updateSectionBoundaries() = updateSectionBoundaries("test")
-
- private interface SectionUpdateState<out T : ExpandableView> {
- val header: T
- var currentPosition: Int?
- var targetPosition: Int?
- fun adjustViewPosition()
- }
-
- private fun <T : ExpandableView> expandableViewHeaderState(header: T): SectionUpdateState<T> =
- object : SectionUpdateState<T> {
- override val header = header
- override var currentPosition: Int? = null
- override var targetPosition: Int? = null
-
- override fun adjustViewPosition() {
- notifPipelineFlags.checkLegacyPipelineEnabled()
- val target = targetPosition
- val current = currentPosition
- if (target == null) {
- if (current != null) {
- parent.removeView(header)
- }
- } else {
- if (current == null) {
- // If the header is animating away, it will still have a parent, so
- // detach it first
- // TODO: We should really cancel the active animations here. This will
- // happen automatically when the view's intro animation starts, but
- // it's a fragile link.
- header.removeFromTransientContainer()
- parent.addView(header, target)
- } else {
- parent.changeViewPosition(header, target)
- }
- }
- }
- }
-
- private fun <T : StackScrollerDecorView> decorViewHeaderState(
- header: T
- ): SectionUpdateState<T> {
- notifPipelineFlags.checkLegacyPipelineEnabled()
- val inner = expandableViewHeaderState(header)
- return object : SectionUpdateState<T> by inner {
- override fun adjustViewPosition() {
- inner.adjustViewPosition()
- if (targetPosition != null && currentPosition == null) {
- header.isContentVisible = true
- }
- }
- }
- }
-
- /**
- * Should be called whenever notifs are added, removed, or updated. Updates section boundary
- * bookkeeping and adds/moves/removes section headers if appropriate.
- */
- fun updateSectionBoundaries(reason: String) = traceSection("NotifSectionsManager.update") {
- notifPipelineFlags.checkLegacyPipelineEnabled()
- if (!isUsingMultipleSections) {
- return@traceSection
- }
- logger.logStartSectionUpdate(reason)
-
- // The overall strategy here is to iterate over the current children of mParent, looking
- // for where the sections headers are currently positioned, and where each section begins.
- // Then, once we find the start of a new section, we track that position as the "target" for
- // the section header, adjusted for the case where existing headers are in front of that
- // target, but won't be once they are moved / removed after the pass has completed.
-
- val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD
- val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled()
-
- val mediaState = mediaControlsView?.let(::expandableViewHeaderState)
- val incomingState = incomingHeaderView?.let(::decorViewHeaderState)
- val peopleState = peopleHeaderView?.let(::decorViewHeaderState)
- val alertingState = alertingHeaderView?.let(::decorViewHeaderState)
- val gentleState = silentHeaderView?.let(::decorViewHeaderState)
-
- fun getSectionState(view: View): SectionUpdateState<ExpandableView>? = when {
- view === mediaControlsView -> mediaState
- view === incomingHeaderView -> incomingState
- view === peopleHeaderView -> peopleState
- view === alertingHeaderView -> alertingState
- view === silentHeaderView -> gentleState
- else -> null
- }
-
- val headersOrdered = sequenceOf(
- mediaState, incomingState, peopleState, alertingState, gentleState
- ).filterNotNull()
-
- var peopleNotifsPresent = false
- var nextBucket: Int? = null
- var inIncomingSection = false
-
- // Iterating backwards allows for easier construction of the Incoming section, as opposed
- // to backtracking when a discontinuity in the sections is discovered.
- // Iterating to -1 in order to support the case where a header is at the very top of the
- // shade.
- for (i in parent.childCount - 1 downTo -1) {
- val child: View? = parent.getChildAt(i)
-
- child?.let {
- logShadeChild(i, child)
- // If this child is a header, update the tracked positions
- getSectionState(child)?.let { state ->
- state.currentPosition = i
- // If headers that should appear above this one in the shade already have a
- // target index, then we need to decrement them in order to account for this one
- // being either removed, or moved below them.
- headersOrdered.takeUntil { it === state }
- .forEach { it.targetPosition = it.targetPosition?.minus(1) }
- }
- }
-
- val row = (child as? ExpandableNotificationRow)
- ?.takeUnless { it.visibility == View.GONE }
-
- // Is there a section discontinuity? This usually occurs due to HUNs
- inIncomingSection = inIncomingSection || nextBucket?.let { next ->
- row?.entry?.bucket?.let { curr -> next < curr }
- } == true
-
- if (inIncomingSection) {
- // Update the bucket to reflect that it's being placed in the Incoming section
- row?.entry?.bucket = BUCKET_HEADS_UP
- }
-
- // Insert a header in front of the next row, if there's a boundary between it and this
- // row, or if it is the topmost row.
- val isSectionBoundary = nextBucket != null &&
- (child == null || row != null && nextBucket != row.entry.bucket)
- if (isSectionBoundary && showHeaders) {
- when (nextBucket) {
- BUCKET_SILENT -> gentleState?.targetPosition = i + 1
- }
- }
-
- row ?: continue
-
- // Check if there are any people notifications
- peopleNotifsPresent = peopleNotifsPresent || row.entry.bucket == BUCKET_PEOPLE
- nextBucket = row.entry.bucket
- }
-
- mediaState?.targetPosition = if (usingMediaControls) 0 else null
-
- logger.logStr("New header target positions:")
- logger.logMediaControls(mediaState?.targetPosition ?: -1)
- logger.logIncomingHeader(incomingState?.targetPosition ?: -1)
- logger.logConversationsHeader(peopleState?.targetPosition ?: -1)
- logger.logAlertingHeader(alertingState?.targetPosition ?: -1)
- logger.logSilentHeader(gentleState?.targetPosition ?: -1)
-
- // Update headers in reverse order to preserve indices, otherwise movements earlier in the
- // list will affect the target indices of the headers later in the list.
- headersOrdered.asIterable().reversed().forEach { it.adjustViewPosition() }
-
- logger.logStr("Final order:")
- logShadeContents()
- logger.logStr("Section boundary update complete")
-
- // Update headers to reflect state of section contents
- silentHeaderView?.run {
- val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
- .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
- setClearSectionButtonEnabled(hasActiveClearableNotifications)
- }
- }
-
private sealed class SectionBounds {
data class Many(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 427004e..952bafb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5822,12 +5822,6 @@
mSpeedBumpIndexDirty = true;
}
- /** Updates the indices of the boundaries between sections. */
- @ShadeViewRefactor(RefactorComponent.INPUT)
- public void updateSectionBoundaries(String reason) {
- mSectionsManager.updateSectionBoundaries(reason);
- }
-
void updateContinuousBackgroundDrawing() {
boolean continuousBackground = !mAmbientState.isFullyAwake()
&& mSwipeHelper.isSwiping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index cc539b0..9998fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -90,7 +90,6 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -162,7 +161,6 @@
private final NotificationEntryManager mNotificationEntryManager;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
- private final VisualStabilityManager mVisualStabilityManager;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -646,7 +644,6 @@
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
- VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -693,7 +690,6 @@
mNotificationEntryManager = notificationEntryManager;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
- mVisualStabilityManager = visualStabilityManager;
mShadeController = shadeController;
updateResources();
}
@@ -765,8 +761,6 @@
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
- mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
-
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index d2f17e8..b0a5adb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,7 +31,7 @@
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
+import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -171,6 +171,7 @@
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -205,7 +206,6 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -688,7 +688,6 @@
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
Optional<Bubbles> bubblesOptional,
- VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
AccessibilityFloatingMenuController accessibilityFloatingMenuController,
@@ -776,7 +775,6 @@
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mBubblesOptional = bubblesOptional;
- mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mNavigationBarController = navigationBarController;
mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
@@ -2194,7 +2192,8 @@
public void onAnimationEnded() {
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
}
- }, false, sUiEventLogger).show(animationDelay);
+ }, /* isDozing= */ false, RippleShape.CIRCLE,
+ sUiEventLogger).show(animationDelay);
}
@Override
@@ -3912,9 +3911,6 @@
// all notifications
protected NotificationStackScrollLayout mStackScroller;
- // handling reordering
- private final VisualStabilityManager mVisualStabilityManager;
-
protected AccessibilityManager mAccessibilityManager;
protected boolean mDeviceInteractive;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index b99b4ab..4c9c75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -330,14 +330,6 @@
dumpInternal(pw, args);
}
- @Override
- public boolean shouldExtendLifetime(NotificationEntry entry) {
- // We should not defer the removal if reordering isn't allowed since otherwise
- // these won't disappear until reordering is allowed again, which happens only once
- // the notification panel is collapsed again.
- return mVisualStabilityProvider.isReorderingAllowed() && super.shouldExtendLifetime(entry);
- }
-
///////////////////////////////////////////////////////////////////////////////////////////////
// OnReorderingAllowedListener:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 339f371..d24469e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -39,6 +39,8 @@
* A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
*/
public class KeyguardIndicationTextView extends TextView {
+ public static final long Y_IN_DURATION = 600L;
+
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
@StyleRes
@@ -259,7 +261,7 @@
private long getYInDuration() {
if (!mAnimationsEnabled) return 0L;
- return 600L;
+ return Y_IN_DURATION;
}
private long getFadeOutDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
index 96b9aca..2763750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -17,11 +17,14 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
+import android.app.WallpaperManager
import android.graphics.Color
+import android.os.Handler
import android.os.RemoteException
import android.view.IWindowManager
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -37,6 +40,8 @@
private val windowManager: IWindowManager,
@Background private val backgroundExecutor: Executor,
private val dumpManager: DumpManager,
+ private val wallpaperManager: WallpaperManager,
+ @Main private val mainHandler: Handler,
) : CentralSurfacesComponent.Startable, Dumpable {
@ColorInt
@@ -46,9 +51,18 @@
var isLetterboxBackgroundMultiColored: Boolean = false
private set
+ private val wallpaperColorsListener =
+ WallpaperManager.OnColorsChangedListener { _, _ ->
+ fetchBackgroundColorInfo()
+ }
+
override fun start() {
dumpManager.registerDumpable(javaClass.simpleName, this)
+ fetchBackgroundColorInfo()
+ wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler)
+ }
+ private fun fetchBackgroundColorInfo() {
// Using a background executor, as binder calls to IWindowManager are blocking
backgroundExecutor.execute {
try {
@@ -62,6 +76,7 @@
override fun stop() {
dumpManager.unregisterDumpable(javaClass.simpleName)
+ wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener)
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
deleted file mode 100644
index ca6e67e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ /dev/null
@@ -1,717 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
-import static com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.logGroupKey;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.os.SystemClock;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.util.Compile;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-/**
- * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
- * and {@link HeadsUpManager}. In particular, this class deals with keeping
- * the correct notification in a group alerting based off the group suppression and alertOverride.
- */
-@SysUISingleton
-public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
- StateListener {
-
- private static final long ALERT_TRANSFER_TIMEOUT = 300;
- private static final String TAG = "NotifGroupAlertTransfer";
- private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
-
- /**
- * The list of entries containing group alert metadata for each group. Keyed by group key.
- */
- private final ArrayMap<String, GroupAlertEntry> mGroupAlertEntries = new ArrayMap<>();
-
- /**
- * The list of entries currently inflating that should alert after inflation. Keyed by
- * notification key.
- */
- private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
-
- private HeadsUpManager mHeadsUpManager;
- private final RowContentBindStage mRowContentBindStage;
- private final NotificationGroupManagerLegacy mGroupManager;
-
- private NotificationEntryManager mEntryManager;
-
- private boolean mIsDozing;
-
- /**
- * Injected constructor. See {@link StatusBarPhoneModule}.
- */
- @Inject
- public NotificationGroupAlertTransferHelper(
- RowContentBindStage bindStage,
- StatusBarStateController statusBarStateController,
- NotificationGroupManagerLegacy notificationGroupManagerLegacy) {
- mRowContentBindStage = bindStage;
- mGroupManager = notificationGroupManagerLegacy;
- statusBarStateController.addCallback(this);
- }
-
- /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
- public void bind(NotificationEntryManager entryManager,
- NotificationGroupManagerLegacy groupManager) {
- if (mEntryManager != null) {
- throw new IllegalStateException("Already bound.");
- }
-
- // TODO(b/119637830): It would be good if GroupManager already had all pending notifications
- // as normal children (i.e. add notifications to GroupManager before inflation) so that we
- // don't have to have this dependency. We'd also have to worry less about the suppression
- // not being up to date.
- mEntryManager = entryManager;
-
- mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
- groupManager.registerGroupChangeListener(mOnGroupChangeListener);
- }
-
- /**
- * Whether or not a notification has transferred its alert state to the notification and
- * the notification should alert after inflating.
- *
- * @param entry notification to check
- * @return true if the entry was transferred to and should inflate + alert
- */
- public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
- PendingAlertInfo alertInfo = mPendingAlerts.get(entry.getKey());
- return alertInfo != null && alertInfo.isStillValid();
- }
-
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- @Override
- public void onStateChanged(int newState) {}
-
- @Override
- public void onDozingChanged(boolean isDozing) {
- if (mIsDozing != isDozing) {
- for (GroupAlertEntry groupAlertEntry : mGroupAlertEntries.values()) {
- groupAlertEntry.mLastAlertTransferTime = 0;
- groupAlertEntry.mAlertSummaryOnNextAddition = false;
- }
- }
- mIsDozing = isDozing;
- }
-
- private final NotificationGroupManagerLegacy.OnGroupChangeListener mOnGroupChangeListener =
- new NotificationGroupManagerLegacy.OnGroupChangeListener() {
- @Override
- public void onGroupCreated(NotificationGroup group, String groupKey) {
- mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
- }
-
- @Override
- public void onGroupRemoved(NotificationGroup group, String groupKey) {
- mGroupAlertEntries.remove(groupKey);
- }
-
- @Override
- public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
- if (DEBUG) {
- Log.d(TAG, "!! onGroupSuppressionChanged:"
- + " group=" + logGroupKey(group)
- + " group.summary=" + logKey(group.summary)
- + " suppressed=" + suppressed);
- }
- NotificationEntry oldAlertOverride = group.alertOverride;
- onGroupChanged(group, oldAlertOverride);
- }
-
- @Override
- public void onGroupAlertOverrideChanged(NotificationGroup group,
- @Nullable NotificationEntry oldAlertOverride,
- @Nullable NotificationEntry newAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "!! onGroupAlertOverrideChanged:"
- + " group=" + logGroupKey(group)
- + " group.summary=" + logKey(group.summary)
- + " oldAlertOverride=" + logKey(oldAlertOverride)
- + " newAlertOverride=" + logKey(newAlertOverride));
- }
- onGroupChanged(group, oldAlertOverride);
- }
- };
-
- /**
- * Called when either the suppressed or alertOverride fields of the group changed
- *
- * @param group the group which changed
- * @param oldAlertOverride the previous value of group.alertOverride
- */
- private void onGroupChanged(NotificationGroup group,
- NotificationEntry oldAlertOverride) {
- // Group summary can be null if we are no longer suppressed because the summary was
- // removed. In that case, we don't need to alert the summary.
- if (group.summary == null) {
- if (DEBUG) {
- Log.d(TAG, "onGroupChanged: summary is null");
- }
- return;
- }
- if (group.suppressed || group.alertOverride != null) {
- checkForForwardAlertTransfer(group.summary, oldAlertOverride);
- } else {
- if (DEBUG) {
- Log.d(TAG, "onGroupChanged: maybe transfer back");
- }
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
- group.summary.getSbn()));
- // Group is no longer suppressed or overridden.
- // We should check if we need to transfer the alert back to the summary.
- if (groupAlertEntry.mAlertSummaryOnNextAddition) {
- if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
- alertNotificationWhenPossible(group.summary);
- }
- groupAlertEntry.mAlertSummaryOnNextAddition = false;
- } else {
- checkShouldTransferBack(groupAlertEntry);
- }
- }
- }
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- if (DEBUG) {
- Log.d(TAG, "!! onHeadsUpStateChanged:"
- + " entry=" + logKey(entry)
- + " isHeadsUp=" + isHeadsUp);
- }
- if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
- // a group summary is alerting; trigger the forward transfer checks
- checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
- }
- }
-
- /**
- * Handles changes in a group's suppression or alertOverride, but where at least one of those
- * conditions is still true (either the group is suppressed, the group has an alertOverride,
- * or both). The method determined which kind of child needs to receive the alert, finds the
- * entry currently alerting, and makes the transfer.
- *
- * Internally, this is handled with two main cases: the override needs the alert, or there is
- * no override but the summary is suppressed (so an isolated child needs the alert).
- *
- * @param summary the notification entry of the summary of the logical group.
- * @param oldAlertOverride the former value of group.alertOverride, before whatever event
- * required us to check for for a transfer condition.
- */
- private void checkForForwardAlertTransfer(NotificationEntry summary,
- NotificationEntry oldAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "checkForForwardAlertTransfer: enter");
- }
- NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
- if (group != null && group.alertOverride != null) {
- handleOverriddenSummaryAlerted(summary);
- } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
- handleSuppressedSummaryAlerted(summary, oldAlertOverride);
- }
- if (DEBUG) {
- Log.d(TAG, "checkForForwardAlertTransfer: done");
- }
- }
-
- private final NotificationEntryListener mNotificationEntryListener =
- new NotificationEntryListener() {
- // Called when a new notification has been posted but is not inflated yet. We use this to
- // see as early as we can if we need to abort a transfer.
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- if (DEBUG) {
- Log.d(TAG, "!! onPendingEntryAdded: entry=" + logKey(entry));
- }
- String groupKey = mGroupManager.getGroupKey(entry.getSbn());
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
- if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
- // new pending group entries require us to transfer back from the child to the
- // group, but alertOverrides are only present in very limited circumstances, so
- // while it's possible the group should ALSO alert, the previous detection which set
- // this alertOverride won't be invalidated by this notification added to this group.
- checkShouldTransferBack(groupAlertEntry);
- }
- }
-
- @Override
- public void onEntryRemoved(
- @Nullable NotificationEntry entry,
- NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- // Removes any alerts pending on this entry. Note that this will not stop any inflation
- // tasks started by a transfer, so this should only be used as clean-up for when
- // inflation is stopped and the pending alert no longer needs to happen.
- mPendingAlerts.remove(entry.getKey());
- }
- };
-
- /**
- * Gets the number of new notifications pending inflation that will be added to the group
- * but currently aren't and should not alert.
- *
- * @param group group to check
- * @return the number of new notifications that will be added to the group
- */
- private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
- if (mEntryManager == null) {
- return 0;
- }
- int number = 0;
- Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
- for (NotificationEntry entry : values) {
- if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
- number++;
- }
- }
- return number;
- }
-
- /**
- * Checks if the pending inflations will add children to this group.
- *
- * @param group group to check
- * @return true if a pending notification will add to this group
- */
- private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
- if (mEntryManager == null) {
- return false;
- }
- Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
- for (NotificationEntry entry : values) {
- if (isPendingNotificationInGroup(entry, group)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Checks if a new pending notification will be added to the group.
- *
- * @param entry pending notification
- * @param group group to check
- * @return true if the notification will add to the group, false o/w
- */
- private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
- @NonNull NotificationGroup group) {
- String groupKey = mGroupManager.getGroupKey(group.summary.getSbn());
- return mGroupManager.isGroupChild(entry.getSbn())
- && Objects.equals(mGroupManager.getGroupKey(entry.getSbn()), groupKey)
- && !group.children.containsKey(entry.getKey());
- }
-
- /**
- * Handles the scenario where a summary that has been suppressed is itself, or has a former
- * alertOverride (in the form of an isolated logical child) which was alerted. A suppressed
- * summary should for all intents and purposes be invisible to the user and as a result should
- * not alert. When this is the case, it is our responsibility to pass the alert to the
- * appropriate child which will be the representative notification alerting for the group.
- *
- * @param summary the summary that is suppressed and (potentially) alerting
- * @param oldAlertOverride the alertOverride before whatever event triggered this method. If
- * the alert override was removed, this will be the entry that should
- * be transferred back from.
- */
- private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
- NotificationEntry oldAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + logKey(summary));
- }
- GroupAlertEntry groupAlertEntry =
- mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-
- if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
- || groupAlertEntry == null) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
- }
- return;
- }
- boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
- boolean priorityIsAlerting = oldAlertOverride != null
- && mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
- if (!summaryIsAlerting && !priorityIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
- }
- return;
- }
-
- if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
- // New children will actually be added to this group, let's not transfer the alert.
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
- }
- return;
- }
-
- NotificationEntry child =
- mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
- if (summaryIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
- }
- tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
- return;
- }
- // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
- // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
- // the isolated child which should receive the alert.
- if (!canStillTransferBack(groupAlertEntry)) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
- }
- tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
- }
-
- /**
- * Checks for and handles the scenario where the given entry is the summary of a group which
- * has an alertOverride, and either the summary itself or one of its logical isolated children
- * is currently alerting (which happens if the summary is suppressed).
- */
- private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + logKey(summary));
- }
- GroupAlertEntry groupAlertEntry =
- mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
- NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
- if (group == null || group.alertOverride == null || groupAlertEntry == null) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
- }
- return;
- }
- boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
- if (summaryIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
- }
- tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
- return;
- }
- // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
- // it's not too late to transfer back, then remove the alert from any of the logical
- // children, and if one of them was alerting, we can alert the override.
- if (!canStillTransferBack(groupAlertEntry)) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
- }
- return;
- }
- List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
- if (children == null) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
- }
- return;
- }
- children.remove(group.alertOverride); // do not release the alert on our desired destination
- boolean releasedChild = releaseChildAlerts(children);
- if (releasedChild) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
- }
- tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
- } else {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
- }
- }
- }
-
- /**
- * Transfers the alert state one entry to another. We remove the alert from the first entry
- * immediately to have the incorrect one up as short as possible. The second should alert
- * when possible.
- *
- * @param summary entry of the summary
- * @param fromEntry entry to transfer alert from
- * @param toEntry entry to transfer to
- */
- private void tryTransferAlertState(
- NotificationEntry summary,
- NotificationEntry fromEntry,
- NotificationEntry toEntry,
- GroupAlertEntry groupAlertEntry) {
- if (toEntry != null) {
- if (toEntry.getRow().keepInParent()
- || toEntry.isRowRemoved()
- || toEntry.isRowDismissed()) {
- // The notification is actually already removed. No need to alert it.
- return;
- }
- if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
- groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
- }
- if (DEBUG) {
- Log.d(TAG, "transferAlertState:"
- + " fromEntry=" + logKey(fromEntry)
- + " toEntry=" + logKey(toEntry));
- }
- transferAlertState(fromEntry, toEntry);
- }
- }
- private void transferAlertState(@Nullable NotificationEntry fromEntry,
- @NonNull NotificationEntry toEntry) {
- if (fromEntry != null) {
- mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
- }
- alertNotificationWhenPossible(toEntry);
- }
-
- /**
- * Determines if we need to transfer the alert back to the summary from the child and does
- * so if needed.
- *
- * This can happen since notification groups are not delivered as a whole unit and it is
- * possible we erroneously transfer the alert from the summary to the child even though
- * more children are coming. Thus, if a child is added within a certain timeframe after we
- * transfer, we back out and alert the summary again.
- *
- * An alert can only transfer back within a small window of time after a transfer away from the
- * summary to a child happened.
- *
- * @param groupAlertEntry group alert entry to check
- */
- private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
- if (canStillTransferBack(groupAlertEntry)) {
- NotificationEntry summary = groupAlertEntry.mGroup.summary;
-
- if (!onlySummaryAlerts(summary)) {
- return;
- }
- ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
- summary.getSbn());
- int numActiveChildren = children.size();
- int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
- int numChildren = numActiveChildren + numPendingChildren;
- if (numChildren <= 1) {
- return;
- }
- boolean releasedChild = releaseChildAlerts(children);
- if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
- boolean notifyImmediately = numActiveChildren > 1;
- if (notifyImmediately) {
- alertNotificationWhenPossible(summary);
- } else {
- // Should wait until the pending child inflates before alerting.
- groupAlertEntry.mAlertSummaryOnNextAddition = true;
- }
- groupAlertEntry.mLastAlertTransferTime = 0;
- }
- }
- }
-
- private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
- return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
- < ALERT_TRANSFER_TIMEOUT;
- }
-
- private boolean releaseChildAlerts(List<NotificationEntry> children) {
- boolean releasedChild = false;
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
- }
- for (int i = 0; i < children.size(); i++) {
- NotificationEntry entry = children.get(i);
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
- + " onlySummaryAlerts=" + onlySummaryAlerts(entry)
- + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
- + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
- }
- if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
- releasedChild = true;
- mHeadsUpManager.removeNotification(
- entry.getKey(), true /* releaseImmediately */);
- }
- if (mPendingAlerts.containsKey(entry.getKey())) {
- // This is the child that would've been removed if it was inflated.
- releasedChild = true;
- mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
- }
- }
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
- }
- return releasedChild;
- }
-
- /**
- * Tries to alert the notification. If its content view is not inflated, we inflate and continue
- * when the entry finishes inflating the view.
- *
- * @param entry entry to show
- */
- private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
- @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
- final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
- if ((params.getContentViews() & contentFlag) == 0) {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible:"
- + " async requestRebind entry=" + logKey(entry));
- }
- mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
- params.requireContentViews(contentFlag);
- mRowContentBindStage.requestRebind(entry, en -> {
- PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
- if (alertInfo != null) {
- if (alertInfo.isStillValid()) {
- alertNotificationWhenPossible(entry);
- } else {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible:"
- + " markContentViewsFreeable entry=" + logKey(entry));
- }
- // The transfer is no longer valid. Free the content.
- mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
- contentFlag);
- mRowContentBindStage.requestRebind(entry, null);
- }
- }
- });
- return;
- }
- if (mHeadsUpManager.isAlerting(entry.getKey())) {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible:"
- + " continue alerting entry=" + logKey(entry));
- }
- mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
- } else {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible:"
- + " start alerting entry=" + logKey(entry));
- }
- mHeadsUpManager.showNotification(entry);
- }
- }
-
- private boolean onlySummaryAlerts(NotificationEntry entry) {
- return entry.getSbn().getNotification().getGroupAlertBehavior()
- == Notification.GROUP_ALERT_SUMMARY;
- }
-
- /**
- * Information about a pending alert used to determine if the alert is still needed when
- * inflation completes.
- */
- private class PendingAlertInfo {
-
- /**
- * The original notification when the transfer is initiated. This is used to determine if
- * the transfer is still valid if the notification is updated.
- */
- final StatusBarNotification mOriginalNotification;
- final NotificationEntry mEntry;
-
- /**
- * The notification is still pending inflation but we've decided that we no longer need
- * the content view (e.g. suppression might have changed and we decided we need to transfer
- * back).
- *
- * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
- */
- boolean mAbortOnInflation;
-
- PendingAlertInfo(NotificationEntry entry) {
- mOriginalNotification = entry.getSbn();
- mEntry = entry;
- }
-
- /**
- * Whether or not the pending alert is still valid and should still alert after inflation.
- *
- * @return true if the pending alert should still occur, false o/w
- */
- private boolean isStillValid() {
- if (mAbortOnInflation) {
- // Notification is aborted due to the transfer being explicitly cancelled
- return false;
- }
- if (!mEntry.getSbn().getGroupKey().equals(mOriginalNotification.getGroupKey())) {
- // Groups have changed
- return false;
- }
- if (mEntry.getSbn().getNotification().isGroupSummary()
- != mOriginalNotification.getNotification().isGroupSummary()) {
- // Notification has changed from group summary to not or vice versa
- return false;
- }
- return true;
- }
- }
-
- /**
- * Contains alert metadata for the notification group used to determine when/how the alert
- * should be transferred.
- */
- private static class GroupAlertEntry {
- /**
- * The time when the last alert transfer from summary to child happened.
- */
- long mLastAlertTransferTime;
- boolean mAlertSummaryOnNextAddition;
- final NotificationGroup mGroup;
-
- GroupAlertEntry(NotificationGroup group) {
- this.mGroup = group;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
new file mode 100644
index 0000000..08185af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.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.keyguard
+
+import android.testing.AndroidTestingRunner
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardUserSwitcherAnchorTest : SysuiTestCase() {
+
+ private lateinit var keyguardUserSwitcherAnchor: KeyguardUserSwitcherAnchor
+
+ @Before
+ fun setUp() {
+ keyguardUserSwitcherAnchor = KeyguardUserSwitcherAnchor(context)
+ }
+
+ @Test
+ fun roleDescription_is_set_to_pulldown_menu() {
+ // GIVEN
+ val roleDescriptionString =
+ context.getString(R.string.accessibility_multi_user_list_switcher)
+
+ // WHEN
+ val result = keyguardUserSwitcherAnchor.createAccessibilityNodeInfo()
+
+ // THEN
+ assertThat(
+ AccessibilityNodeInfoCompat.wrap(result).roleDescription
+ ).isEqualTo(roleDescriptionString)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
new file mode 100644
index 0000000..a78886f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase {
+ @Mock
+ DreamOverlayStatusBarItemsProvider.Callback mCallback;
+ @Mock
+ DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
+
+ private final Executor mMainExecutor = Runnable::run;
+
+ DreamOverlayStatusBarItemsProvider mProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mProvider = new DreamOverlayStatusBarItemsProvider(mMainExecutor);
+ }
+
+ @Test
+ public void addingCallbackCallsOnStatusBarItemsChanged() {
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.addCallback(mCallback);
+ verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void addingStatusBarItemCallsOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void addingDuplicateStatusBarItemDoesNotCallOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ // Called only once for addStatusBarItem.
+ verify(mCallback, times(1))
+ .onStatusBarItemsChanged(List.of(mStatusBarItem));
+ }
+
+ @Test
+ public void removingStatusBarItemCallsOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.addStatusBarItem(mStatusBarItem);
+ mProvider.removeStatusBarItem(mStatusBarItem);
+ // Called once for addStatusBarItem and once for removeStatusBarItem.
+ verify(mCallback, times(2)).onStatusBarItemsChanged(any());
+ }
+
+ @Test
+ public void removingNonexistentStatusBarItemDoesNotCallOnStatusBarItemsChanged() {
+ mProvider.addCallback(mCallback);
+ mProvider.removeStatusBarItem(mStatusBarItem);
+ verify(mCallback, never()).onStatusBarItemsChanged(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 60e5a94..01309f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -57,6 +57,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -94,6 +95,12 @@
DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider;
@Mock
StatusBarWindowStateController mStatusBarWindowStateController;
+ @Mock
+ DreamOverlayStatusBarItemsProvider mDreamOverlayStatusBarItemsProvider;
+ @Mock
+ DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem;
+ @Mock
+ View mStatusBarItemView;
private final Executor mMainExecutor = Runnable::run;
@@ -118,7 +125,8 @@
mSensorPrivacyController,
Optional.of(mDreamOverlayNotificationCountProvider),
mZenModeController,
- mStatusBarWindowStateController);
+ mStatusBarWindowStateController,
+ mDreamOverlayStatusBarItemsProvider);
}
@Test
@@ -128,6 +136,7 @@
verify(mSensorPrivacyController).addCallback(any());
verify(mZenModeController).addCallback(any());
verify(mDreamOverlayNotificationCountProvider).addCallback(any());
+ verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());
}
@Test
@@ -256,7 +265,8 @@
mSensorPrivacyController,
Optional.empty(),
mZenModeController,
- mStatusBarWindowStateController);
+ mStatusBarWindowStateController,
+ mDreamOverlayStatusBarItemsProvider);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
@@ -294,6 +304,7 @@
verify(mSensorPrivacyController).removeCallback(any());
verify(mZenModeController).removeCallback(any());
verify(mDreamOverlayNotificationCountProvider).removeCallback(any());
+ verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any());
}
@Test
@@ -462,4 +473,18 @@
verify(mView, never()).setVisibility(anyInt());
}
+
+ @Test
+ public void testExtraStatusBarItemSetWhenItemsChange() {
+ mController.onViewAttached();
+ when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
+
+ final ArgumentCaptor<DreamOverlayStatusBarItemsProvider.Callback>
+ callbackCapture = ArgumentCaptor.forClass(
+ DreamOverlayStatusBarItemsProvider.Callback.class);
+ verify(mDreamOverlayStatusBarItemsProvider).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStatusBarItemsChanged(List.of(mStatusBarItem));
+
+ verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 6fff440..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.systemui.keyguard.data.repository
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
-
- private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
- val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
-
- var onClickedResult: OnClickedResult = OnClickedResult.Handled
-
- private val _state =
- MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
- KeyguardQuickAffordanceConfig.State.Hidden
- )
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
-
- override fun onQuickAffordanceClicked(
- animationController: ActivityLaunchAnimator.Controller?,
- ): OnClickedResult {
- _onClickedInvocations.add(animationController)
- return onClickedResult
- }
-
- suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
- _state.value = state
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
deleted file mode 100644
index a24fc93..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
-
-/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
-class FakeKeyguardQuickAffordanceConfigs(
- private val configsByPosition:
- Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceConfigs {
-
- override fun getAll(
- position: KeyguardQuickAffordancePosition
- ): List<KeyguardQuickAffordanceConfig> {
- return configsByPosition.getValue(position)
- }
-
- override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
- ): KeyguardQuickAffordanceConfig {
- return configsByPosition.values
- .flatten()
- .associateBy { config -> config::class }
- .getValue(configClass)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
deleted file mode 100644
index 10d2e4d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.yield
-
-/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
-class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
-
- private val modelByPosition =
- mutableMapOf<
- KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
- init {
- KeyguardQuickAffordancePosition.values().forEach { value ->
- modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
- }
- }
-
- override fun affordance(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return modelByPosition.getValue(position)
- }
-
- suspend fun setModel(
- position: KeyguardQuickAffordancePosition,
- model: KeyguardQuickAffordanceModel
- ) {
- modelByPosition.getValue(position).value = model
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index d40b985..38a3375 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -20,6 +20,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.yield
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository {
@@ -43,11 +44,6 @@
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
- init {
- setDozeAmount(0f)
- setDozing(false)
- }
-
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -60,15 +56,30 @@
_clockPosition.value = Position(x, y)
}
- fun setKeyguardShowing(isShowing: Boolean) {
+ suspend fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
}
- fun setDozing(isDozing: Boolean) {
+ suspend fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
}
- fun setDozeAmount(dozeAmount: Float) {
+ suspend fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
deleted file mode 100644
index dc0e6f7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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.systemui.keyguard.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlin.reflect.KClass
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
-
- private lateinit var underTest: KeyguardQuickAffordanceRepository
-
- private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
- private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
- private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
-
- underTest =
- KeyguardQuickAffordanceRepositoryImpl(
- configs =
- FakeKeyguardQuickAffordanceConfigs(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControls,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWallet,
- qrCodeScanner,
- ),
- ),
- ),
- )
- }
-
- @Test
- fun `bottom start affordance - none`() = runBlockingTest {
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
-
- @Test
- fun `bottom start affordance - home controls`() = runBlockingTest {
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
-
- val state =
- KeyguardQuickAffordanceConfig.State.Visible(
- icon = mock(),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- homeControls.setState(state)
-
- assertThat(latest).isEqualTo(state.toModel(homeControls::class))
- job.cancel()
- }
-
- @Test
- fun `bottom end affordance - none`() = runBlockingTest {
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
-
- @Test
- fun `bottom end affordance - quick access wallet`() = runBlockingTest {
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
-
- val quickAccessWalletState =
- KeyguardQuickAffordanceConfig.State.Visible(
- icon = mock(),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- quickAccessWallet.setState(quickAccessWalletState)
- val qrCodeScannerState =
- KeyguardQuickAffordanceConfig.State.Visible(
- icon = mock(),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- qrCodeScanner.setState(qrCodeScannerState)
-
- assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
- job.cancel()
- }
-
- @Test
- fun `bottom end affordance - qr code scanner`() = runBlockingTest {
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
-
- val state =
- KeyguardQuickAffordanceConfig.State.Visible(
- icon = mock(),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- qrCodeScanner.setState(state)
-
- assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
- job.cancel()
- }
-
- private fun KeyguardQuickAffordanceConfig.State?.toModel(
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
- ): KeyguardQuickAffordanceModel? {
- return when (this) {
- is KeyguardQuickAffordanceConfig.State.Visible ->
- KeyguardQuickAffordanceModel.Visible(
- configKey = configKey,
- icon = icon,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
- null -> null
- }
- }
-
- companion object {
- private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6ea1daa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+ var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+ private val _state =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+ KeyguardQuickAffordanceConfig.State.Hidden
+ )
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): OnClickedResult {
+ return onClickedResult
+ }
+
+ suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+ _state.value = state
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
new file mode 100644
index 0000000..1c9902b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.systemui.keyguard.domain.quickaffordance
+
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
+class FakeKeyguardQuickAffordanceRegistry(
+ private val configsByPosition:
+ Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry {
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configsByPosition.values
+ .flatten()
+ .associateBy { config -> config::class }
+ .getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index bcc76ab..9acd21c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,20 +1,21 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
*/
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.keyguard.domain.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -22,11 +23,10 @@
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -50,18 +50,19 @@
companion object {
@Parameters(
name =
- "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
- " visible = {3}"
+ "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
+ " while locked = {3} - expected visible = {4}"
)
@JvmStatic
fun data() =
- (0 until 8)
+ (0 until 16)
.map { combination ->
arrayOf(
- /* isFeatureEnabled= */ combination and 0b100 != 0,
- /* hasFavorites= */ combination and 0b010 != 0,
- /* hasServiceInfos= */ combination and 0b001 != 0,
- /* isVisible= */ combination == 0b111,
+ /* isFeatureEnabled= */ combination and 0b1000 != 0,
+ /* hasFavorites= */ combination and 0b0100 != 0,
+ /* hasServiceInfos= */ combination and 0b0010 != 0,
+ /* canShowWhileLocked= */ combination and 0b0001 != 0,
+ /* isVisible= */ combination == 0b1111,
)
}
.toList()
@@ -79,7 +80,8 @@
@JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
@JvmField @Parameter(1) var hasFavorites: Boolean = false
@JvmField @Parameter(2) var hasServiceInfos: Boolean = false
- @JvmField @Parameter(3) var isVisible: Boolean = false
+ @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(4) var isVisible: Boolean = false
@Before
fun setUp() {
@@ -89,6 +91,8 @@
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ whenever(component.canShowWhileLockedSetting)
+ .thenReturn(MutableStateFlow(canShowWhileLocked))
underTest =
HomeControlsKeyguardQuickAffordanceConfig(
@@ -111,14 +115,16 @@
val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
val job = underTest.state.onEach(values::add).launchIn(this)
- verify(controlsListingController).addCallback(callbackCaptor.capture())
- callbackCaptor.value.onServicesUpdated(
- if (hasServiceInfos) {
- listOf(mock())
- } else {
- emptyList()
- }
- )
+ if (canShowWhileLocked) {
+ verify(controlsListingController).addCallback(callbackCaptor.capture())
+ callbackCaptor.value.onServicesUpdated(
+ if (hasServiceInfos) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+ }
assertThat(values.last())
.isInstanceOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
similarity index 73%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 592e80b..059487d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -23,7 +23,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
@@ -51,6 +51,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
underTest =
HomeControlsKeyguardQuickAffordanceConfig(
@@ -60,7 +61,26 @@
}
@Test
- fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+ fun `state - when cannot show while locked - returns Hidden`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+ whenever(component.isEnabled()).thenReturn(true)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ val controlsController = mock<ControlsController>()
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+ whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ assertThat(values.last())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ job.cancel()
+ }
+
+ @Test
+ fun `state - when listing controller is missing - returns Hidden`() = runBlockingTest {
whenever(component.isEnabled()).thenReturn(true)
whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 6fd04de..d4fba41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -15,12 +15,12 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 345c51f..5a3a78e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyguard.data.quickaffordance
+package com.android.systemui.keyguard.domain.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..8982752
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
+
+ private val affordanceByPosition =
+ mutableMapOf<
+ KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+ init {
+ KeyguardQuickAffordancePosition.values().forEach { position ->
+ affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+ }
+ }
+
+ override fun invoke(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
+ }
+
+ fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
+ affordanceByPosition[position]?.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 0000000..63eb68f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.systemui.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() = runBlockingTest {
+ repository = FakeKeyguardRepository()
+ repository.setKeyguardShowing(true)
+ isDozingUseCase = ObserveIsDozingUseCase(repository)
+ isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ ObserveKeyguardQuickAffordanceUseCaseImpl(
+ registry =
+ FakeKeyguardQuickAffordanceRegistry(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ ),
+ ),
+ isDozingUseCase = isDozingUseCase,
+ isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ )
+ }
+
+ @Test
+ fun `invoke - bottom start affordance is visible`() = runBlockingTest {
+ val configKey = homeControls::class
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.contentDescriptionResourceId)
+ .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - bottom end affordance is visible`() = runBlockingTest {
+ val configKey = quickAccessWallet::class
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.contentDescriptionResourceId)
+ .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest {
+ repository.setDozing(true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - bottom start affordance hidden when lockscreen is not showing`() =
+ runBlockingTest {
+ repository.setKeyguardShowing(false)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ companion object {
+ private val ICON: ContainedDrawable = mock()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
deleted file mode 100644
index b90400be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.systemui.keyguard.domain.usecase
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.containeddrawable.ContainedDrawable
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
-
- private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
-
- private lateinit var repository: FakeKeyguardRepository
- private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
- private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
-
- @Before
- fun setUp() {
- repository = FakeKeyguardRepository()
- repository.setKeyguardShowing(true)
- isDozingUseCase = ObserveIsDozingUseCase(repository)
- isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
- quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
-
- underTest =
- ObserveKeyguardQuickAffordanceUseCase(
- repository = quickAffordanceRepository,
- isDozingUseCase = isDozingUseCase,
- isKeyguardShowingUseCase = isKeyguardShowingUseCase,
- )
- }
-
- @Test
- fun `invoke - affordance is visible`() = runBlockingTest {
- val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
- val model =
- KeyguardQuickAffordanceModel.Visible(
- configKey = configKey,
- icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- quickAffordanceRepository.setModel(
- KeyguardQuickAffordancePosition.BOTTOM_END,
- model,
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
- val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
- assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.contentDescriptionResourceId)
- .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
- job.cancel()
- }
-
- @Test
- fun `invoke - affordance not visible while dozing`() = runBlockingTest {
- repository.setDozing(true)
- val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
- val model =
- KeyguardQuickAffordanceModel.Visible(
- configKey = configKey,
- icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- quickAffordanceRepository.setModel(
- KeyguardQuickAffordancePosition.BOTTOM_END,
- model,
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
-
- @Test
- fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
- repository.setKeyguardShowing(false)
- val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
- val model =
- KeyguardQuickAffordanceModel.Visible(
- configKey = configKey,
- icon = ICON,
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- quickAffordanceRepository.setModel(
- KeyguardQuickAffordancePosition.BOTTOM_END,
- model,
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
-
- @Test
- fun `invoke - affordance is none`() = runBlockingTest {
- quickAffordanceRepository.setModel(
- KeyguardQuickAffordancePosition.BOTTOM_START,
- KeyguardQuickAffordanceModel.Hidden,
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
-
- companion object {
- private val ICON: ContainedDrawable = mock()
- private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 00dd58e..8758ce5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -22,22 +22,20 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
-import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsKeyguardShowingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -63,14 +61,14 @@
private lateinit var underTest: KeyguardBottomAreaViewModel
- private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
private lateinit var repository: FakeKeyguardRepository
+ private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
@Before
fun setUp() {
@@ -78,33 +76,38 @@
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
- affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+ homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ registry =
+ FakeKeyguardQuickAffordanceRegistry(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControlsQuickAffordanceConfig,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ ),
+ )
repository = FakeKeyguardRepository()
isDozingUseCase =
ObserveIsDozingUseCase(
repository = repository,
)
- isKeyguardShowingUseCase =
- ObserveIsKeyguardShowingUseCase(
- repository = repository,
- )
launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
- homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
underTest =
KeyguardBottomAreaViewModel(
- observeQuickAffordanceUseCase =
- ObserveKeyguardQuickAffordanceUseCase(
- repository = affordanceRepository,
- isDozingUseCase = isDozingUseCase,
- isKeyguardShowingUseCase = isKeyguardShowingUseCase,
- ),
+ observeQuickAffordanceUseCase = observeQuickAffordanceUseCase,
onQuickAffordanceClickedUseCase =
OnKeyguardQuickAffordanceClickedUseCase(
- configs =
- FakeKeyguardQuickAffordanceConfigs(
+ registry =
+ FakeKeyguardQuickAffordanceRegistry(
mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to
listOf(
@@ -141,98 +144,11 @@
}
@Test
- fun `startButton - present - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
- runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
- repository.setDozing(false)
- repository.setKeyguardShowing(true)
- val testConfig =
- TestConfig(
- isVisible = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun `endButton - present - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
- runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.endButton.onEach { latest = it }.launchIn(this)
-
- repository.setDozing(false)
- repository.setKeyguardShowing(true)
- val config =
- TestConfig(
- isVisible = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent =
- null, // This will cause it to tell the system that the click was handled.
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun `startButton - not present - not dozing - lockscreen showing - model is none`() =
- runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
- repository.setDozing(false)
- repository.setKeyguardShowing(true)
- val config =
- TestConfig(
- isVisible = false,
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
-
- @Test
- fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest {
+ fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setDozing(true)
- repository.setKeyguardShowing(true)
- val config =
+ val testConfig =
TestConfig(
isVisible = true,
icon = mock(),
@@ -242,45 +158,65 @@
val configKey =
setUpQuickAffordanceModel(
position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = config,
+ testConfig = testConfig,
)
assertQuickAffordanceViewModel(
viewModel = latest,
- testConfig = TestConfig(isVisible = false),
+ testConfig = testConfig,
configKey = configKey,
)
job.cancel()
}
@Test
- fun `startButton - present - not dozing - lockscreen not showing - model is none`() =
- runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+ fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.endButton.onEach { latest = it }.launchIn(this)
- repository.setDozing(false)
- repository.setKeyguardShowing(false)
- val config =
- TestConfig(
- isVisible = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = TestConfig(isVisible = false),
- configKey = configKey,
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = null, // This will cause it to tell the system that the click was handled.
)
- job.cancel()
- }
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - not present - model is hidden`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ val config =
+ TestConfig(
+ isVisible = false,
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
@Test
fun animateButtonReveal() = runBlockingTest {
@@ -413,7 +349,7 @@
job.cancel()
}
- private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
repository.setDozeAmount(dozeAmount)
return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
}
@@ -428,27 +364,31 @@
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
}
- affordanceRepository.setModel(
- position = position,
- model =
- if (testConfig.isVisible) {
- if (testConfig.intent != null) {
- config.onClickedResult =
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
- intent = testConfig.intent,
- canShowWhileLocked = testConfig.canShowWhileLocked,
- )
- }
- KeyguardQuickAffordanceModel.Visible(
- configKey = config::class,
- icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- } else {
- KeyguardQuickAffordanceModel.Hidden
+ val state =
+ if (testConfig.isVisible) {
+ if (testConfig.intent != null) {
+ config.onClickedResult =
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = testConfig.intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ )
}
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ config.setState(state)
+
+ val configKey = config::class
+ observeQuickAffordanceUseCase.setModel(
+ position,
+ KeyguardQuickAffordanceModel.from(state, configKey)
)
- return config::class
+
+ return configKey
}
private fun assertQuickAffordanceViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 6a532d7..6468fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
-import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -29,18 +29,18 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
@@ -433,7 +433,7 @@
val dataCurrentAndActive = dataCurrent.copy(active = true)
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
eq(100), eq(true))
- assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+ assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 568e0cb..260bb87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -254,4 +254,30 @@
verify(mMediaOutputController).connectDevice(mMediaDevice2);
}
+
+ @Test
+ public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
+ when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
+ List<MediaDevice> selectableDevices = new ArrayList<>();
+ selectableDevices.add(mMediaDevice1);
+ when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+ when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mContainerLayout.performClick();
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() {
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
+
+ when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 533c231..314997d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -43,7 +43,6 @@
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -54,7 +53,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.Before;
import org.junit.Test;
@@ -83,8 +82,7 @@
LocalBluetoothLeBroadcast.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
- private NotificationEntryManager mNotificationEntryManager =
- mock(NotificationEntryManager.class);
+ private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -120,7 +118,7 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotificationEntryManager, mDialogLaunchAnimator,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 6afed1a..4779d32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -47,7 +47,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
import org.junit.Before;
@@ -78,8 +78,7 @@
private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
private final MediaDevice mMediaDevice = mock(MediaDevice.class);
- private final NotificationEntryManager mNotificationEntryManager =
- mock(NotificationEntryManager.class);
+ private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -104,7 +103,7 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotificationEntryManager, mDialogLaunchAnimator,
+ mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index fba1986..bd913ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -63,7 +63,6 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -196,8 +195,6 @@
@Mock
private PackageManager mPackageManager;
@Mock
- private NotificationEntryManager mNotificationEntryManager;
- @Mock
private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@Mock
private BackupManager mBackupManager;
@@ -234,8 +231,6 @@
when(mMockContext.getString(R.string.over_two_weeks_timestamp)).thenReturn(
mContext.getString(R.string.over_two_weeks_timestamp));
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
- when(mNotificationEntryManager.getVisibleNotifications())
- .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
}
@After
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 642e29b..2ba8782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -22,13 +22,17 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -42,47 +46,38 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class FooterActionsControllerTest : LeakCheckedTest() {
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock
- private lateinit var userInfoController: UserInfoController
- @Mock
- private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
- @Mock
- private lateinit var multiUserSwitchController: MultiUserSwitchController
- @Mock
- private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
- @Mock
- private lateinit var globalActionsDialog: GlobalActionsDialogLite
- @Mock
- private lateinit var uiEventLogger: UiEventLogger
- @Mock
- private lateinit var securityFooterController: QSSecurityFooter
- @Mock
- private lateinit var fgsManagerController: QSFgsManagerFooter
+
+ @get:Rule var expect: Expect = Expect.create()
+
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var userInfoController: UserInfoController
+ @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
+ @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
+ @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
+ @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var securityFooterController: QSSecurityFooter
+ @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
@Captor
private lateinit var visibilityChangedCaptor:
ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
private lateinit var controller: FooterActionsController
+ private val configurationController = FakeConfigurationController()
private val metricsLogger: MetricsLogger = FakeMetricsLogger()
- private lateinit var view: FooterActionsView
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+ private lateinit var view: FooterActionsView
private lateinit var testableLooper: TestableLooper
private lateinit var fakeSettings: FakeSettings
private lateinit var securityFooter: View
@@ -90,12 +85,15 @@
@Before
fun setUp() {
+ // We want to make sure testable resources are always used
+ context.ensureTestableResources()
+
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
fakeSettings = FakeSettings()
whenever(multiUserSwitchControllerFactory.create(any()))
- .thenReturn(multiUserSwitchController)
+ .thenReturn(multiUserSwitchController)
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
securityFooter = View(mContext)
@@ -135,7 +133,7 @@
view.findViewById<View>(R.id.pm_lite).performClick()
// Verify clicks are logged
verify(uiEventLogger, Mockito.times(1))
- .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
}
@Test
@@ -299,6 +297,86 @@
assertThat(booleanCaptor.allValues.last()).isTrue()
}
+ @Test
+ fun setExpansion_inSplitShade_alphaFollowsExpansion() {
+ enableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.25f)
+ expect.that(view.alpha).isEqualTo(0.25f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.alpha).isEqualTo(0.5f)
+
+ controller.setExpansion(0.75f)
+ expect.that(view.alpha).isEqualTo(0.75f)
+
+ controller.setExpansion(1f)
+ expect.that(view.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
+ enableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.9f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
+
+ controller.setExpansion(0.91f)
+ expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+ controller.setExpansion(0.95f)
+ expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+ controller.setExpansion(1f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
+ disableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.9f)
+ expect.that(view.alpha).isEqualTo(0f)
+
+ controller.setExpansion(0.91f)
+ expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)
+
+ controller.setExpansion(0.95f)
+ expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)
+
+ controller.setExpansion(1f)
+ expect.that(view.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
+ disableSplitShade()
+
+ controller.setExpansion(0f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+ controller.setExpansion(0.5f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+
+ controller.setExpansion(1f)
+ expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
+ }
+
private fun setVisibilities(
securityFooterVisible: Boolean,
fgsFooterVisible: Boolean,
@@ -311,15 +389,52 @@
}
private fun inflateView(): FooterActionsView {
- return LayoutInflater.from(context)
- .inflate(R.layout.footer_actions, null) as FooterActionsView
+ return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
+ as FooterActionsView
}
private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
- return FooterActionsController(view, multiUserSwitchControllerFactory,
- activityStarter, userManager, userTracker, userInfoController,
- deviceProvisionedController, securityFooterController, fgsManagerController,
- falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
- showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
+ return FooterActionsController(
+ view,
+ multiUserSwitchControllerFactory,
+ activityStarter,
+ userManager,
+ userTracker,
+ userInfoController,
+ deviceProvisionedController,
+ securityFooterController,
+ fgsManagerController,
+ falsingManager,
+ metricsLogger,
+ globalActionsDialogProvider,
+ uiEventLogger,
+ showPMLiteButton = true,
+ fakeSettings,
+ Handler(testableLooper.looper),
+ configurationController)
}
-}
\ No newline at end of file
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+}
+
+private const val FLOAT_TOLERANCE = 0.01f
+
+private val View.backgroundAlphaFraction: Float?
+ get() {
+ return if (background != null) {
+ background.alpha / 255f
+ } else {
+ null
+ }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 32c66d2..10f6ce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -205,6 +205,40 @@
}
@Test
+ public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
+ // Random test values without any meaning. They just have to be different from each other.
+ float expansion = 0.123f;
+ float panelExpansionFraction = 0.321f;
+ float proposedTranslation = 456f;
+ float squishinessFraction = 0.987f;
+
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+
+ fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
+ }
+
+ @Test
+ public void setQsExpansion_notInSplitShade_setsFooterActionsExpansion_basedOnExpansion() {
+ // Random test values without any meaning. They just have to be different from each other.
+ float expansion = 0.123f;
+ float panelExpansionFraction = 0.321f;
+ float proposedTranslation = 456f;
+ float squishinessFraction = 0.987f;
+
+ QSFragment fragment = resumeAndGetFragment();
+ disableSplitShade();
+
+ fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSFooterActionController).setExpansion(expansion);
+ }
+
+ @Test
public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() {
QSFragment fragment = resumeAndGetFragment();
disableSplitShade();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 7dbc561..3c58b6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -22,6 +22,7 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
@@ -32,11 +33,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
+import android.util.SparseArray;
import android.view.View;
import androidx.annotation.Nullable;
@@ -60,12 +63,14 @@
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.FakeSharedPreferences;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -76,6 +81,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -130,6 +136,10 @@
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
+ @Mock
+ private UserFileManager mUserFileManager;
+
+ private SparseArray<SharedPreferences> mSharedPreferencesByUser;
private FakeExecutor mMainExecutor;
@@ -140,17 +150,29 @@
MockitoAnnotations.initMocks(this);
mMainExecutor = new FakeExecutor(new FakeSystemClock());
+ mSharedPreferencesByUser = new SparseArray<>();
+
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
+ when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenAnswer((Answer<SharedPreferences>) invocation -> {
+ assertEquals(QSTileHost.TILES, invocation.getArgument(0));
+ int userId = invocation.getArgument(2);
+ if (!mSharedPreferencesByUser.contains(userId)) {
+ mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
+ }
+ return mSharedPreferencesByUser.get(userId);
+ });
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+ mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
+ mUserFileManager);
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override
@@ -528,6 +550,118 @@
assertEquals("spec1", getSetting());
}
+ @Test
+ public void testIsTileAdded_true() {
+ int user = mUserTracker.getUserId();
+ getSharedPreferenecesForUser(user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), true)
+ .apply();
+
+ assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_false() {
+ int user = mUserTracker.getUserId();
+ getSharedPreferenecesForUser(user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), false)
+ .apply();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_notSet() {
+ int user = mUserTracker.getUserId();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
+ }
+
+ @Test
+ public void testIsTileAdded_differentUser() {
+ int user = mUserTracker.getUserId();
+ mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
+ .edit()
+ .putBoolean(CUSTOM_TILE.flattenToString(), true)
+ .apply();
+
+ assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
+ }
+
+ @Test
+ public void testSetTileAdded_true() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ assertTrue(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileAdded_false() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
+
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileAdded_differentUser() {
+ int user = mUserTracker.getUserId();
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ assertFalse(getSharedPreferenecesForUser(user + 1)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_afterCustomTileChangedByUser() {
+ int user = mUserTracker.getUserId();
+ saveSetting(CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1"));
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_removedByUser() {
+ int user = mUserTracker.getUserId();
+ saveSetting(CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.removeTileByUser(CUSTOM_TILE);
+ mMainExecutor.runAllReady();
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ @Test
+ public void testSetTileRemoved_removedBySystem() {
+ int user = mUserTracker.getUserId();
+ saveSetting("spec1" + CUSTOM_TILE_SPEC);
+
+ // This will be done by TileServiceManager
+ mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
+
+ mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
+ mMainExecutor.runAllReady();
+ assertFalse(getSharedPreferenecesForUser(user)
+ .getBoolean(CUSTOM_TILE.flattenToString(), false));
+ }
+
+ private SharedPreferences getSharedPreferenecesForUser(int user) {
+ return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Executor mainExecutor,
@@ -537,11 +671,13 @@
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
- TileLifecycleManager.Factory tileLifecycleManagerFactory) {
+ TileLifecycleManager.Factory tileLifecycleManagerFactory,
+ UserFileManager userFileManager) {
super(context, iconController, defaultFactory, mainExecutor, pluginManager,
tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
uiEventLogger, userTracker, secureSettings, customTileStatePersister,
- tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
+ tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
+ userFileManager);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 573980d..8aa625a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -19,6 +19,14 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +39,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
import org.junit.After;
@@ -38,37 +47,45 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileServiceManagerTest extends SysuiTestCase {
+ @Mock
private TileServices mTileServices;
+ @Mock
private TileLifecycleManager mTileLifecycle;
+ @Mock
+ private UserTracker mUserTracker;
+ @Mock
+ private QSTileHost mQSTileHost;
+ @Mock
+ private Context mMockContext;
+
private HandlerThread mThread;
private Handler mHandler;
private TileServiceManager mTileServiceManager;
- private UserTracker mUserTracker;
- private Context mMockContext;
+ private ComponentName mComponentName;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mThread = new HandlerThread("TestThread");
mThread.start();
mHandler = Handler.createAsync(mThread.getLooper());
- mTileServices = Mockito.mock(TileServices.class);
- mUserTracker = Mockito.mock(UserTracker.class);
- Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
- Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
- mMockContext = Mockito.mock(Context.class);
- Mockito.when(mTileServices.getContext()).thenReturn(mMockContext);
- mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
- Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
- ComponentName componentName = new ComponentName(mContext,
- TileServiceManagerTest.class);
- Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
+ when(mTileServices.getContext()).thenReturn(mMockContext);
+ when(mTileServices.getHost()).thenReturn(mQSTileHost);
+ when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId());
+ when(mTileLifecycle.isActiveTile()).thenReturn(false);
+
+ mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
+ when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
mTileLifecycle);
}
@@ -80,17 +97,44 @@
}
@Test
+ public void testSetTileAddedIfNotAdded() {
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+ }
+
+ @Test
+ public void testNotSetTileAddedIfAdded() {
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+ }
+
+ @Test
+ public void testSetTileAddedCorrectUser() {
+ int user = 10;
+ when(mUserTracker.getUserId()).thenReturn(user);
+ when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+
+ verify(mQSTileHost).setTileAdded(mComponentName, user, true);
+ }
+
+ @Test
public void testUninstallReceiverExported() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
ArgumentCaptor<IntentFilter> intentFilterCaptor =
ArgumentCaptor.forClass(IntentFilter.class);
- Mockito.verify(mMockContext).registerReceiverAsUser(
- Mockito.any(),
- Mockito.any(),
+ verify(mMockContext).registerReceiverAsUser(
+ any(),
+ any(),
intentFilterCaptor.capture(),
- Mockito.any(),
- Mockito.any(),
- Mockito.eq(Context.RECEIVER_EXPORTED)
+ any(),
+ any(),
+ eq(Context.RECEIVER_EXPORTED)
);
IntentFilter filter = intentFilterCaptor.getValue();
assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
@@ -99,38 +143,41 @@
@Test
public void testSetBindRequested() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
// Request binding.
mTileServiceManager.setBindRequested(true);
mTileServiceManager.setLastUpdate(0);
mTileServiceManager.calculateBindPriority(5);
- Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+ verify(mTileServices, times(2)).recalculateBindAllowance();
assertEquals(5, mTileServiceManager.getBindPriority());
// Verify same state doesn't trigger recalculating for no reason.
mTileServiceManager.setBindRequested(true);
- Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+ verify(mTileServices, times(2)).recalculateBindAllowance();
mTileServiceManager.setBindRequested(false);
mTileServiceManager.calculateBindPriority(5);
- Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance();
+ verify(mTileServices, times(3)).recalculateBindAllowance();
assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());
}
@Test
public void testPendingClickPriority() {
- Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ when(mTileLifecycle.hasPendingClick()).thenReturn(true);
mTileServiceManager.calculateBindPriority(0);
assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());
}
@Test
public void testBind() {
+ mTileServiceManager.startLifecycleManagerAndAddTile();
// Trigger binding requested and allowed.
mTileServiceManager.setBindRequested(true);
mTileServiceManager.setBindAllowed(true);
ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
- Mockito.verify(mTileLifecycle, Mockito.times(1)).setBindService(captor.capture());
+ verify(mTileLifecycle, times(1)).setBindService(captor.capture());
assertTrue((boolean) captor.getValue());
mTileServiceManager.setBindRequested(false);
@@ -141,7 +188,7 @@
mTileServiceManager.setBindAllowed(false);
captor = ArgumentCaptor.forClass(Boolean.class);
- Mockito.verify(mTileLifecycle, Mockito.times(2)).setBindService(captor.capture());
+ verify(mTileLifecycle, times(2)).setBindService(captor.capture());
assertFalse((boolean) captor.getValue());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 471ddfd..213eca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
@@ -118,6 +119,8 @@
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
+ @Mock
+ private UserFileManager mUserFileManager;
@Before
public void setUp() throws Exception {
@@ -149,7 +152,8 @@
mSecureSettings,
mock(CustomTileStatePersister.class),
mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory);
+ mTileLifecycleManagerFactory,
+ mUserFileManager);
mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,
mUserTracker, mKeyguardStateController, mCommandQueue);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 5336ef0..ba49f3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -37,6 +37,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -78,6 +79,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -144,7 +146,25 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.click(null /* view */);
- verify(mQsLogger).logTileClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE),
+ anyInt());
+ }
+
+ @Test
+ public void testHandleClick_log() {
+ mTile.click(null);
+ mTile.click(null);
+ mTestableLooper.processAllMessages();
+ mTile.click(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleClick(SPEC, 2);
}
@Test
@@ -183,7 +203,25 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.secondaryClick(null /* view */);
- verify(mQsLogger).logTileSecondaryClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE),
+ eq(Tile.STATE_ACTIVE), anyInt());
+ }
+
+ @Test
+ public void testHandleSecondaryClick_log() {
+ mTile.secondaryClick(null);
+ mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
+ mTile.secondaryClick(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 2);
}
@Test
@@ -210,7 +248,25 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mTile.longClick(null /* view */);
- verify(mQsLogger).logTileLongClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE);
+ verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE),
+ eq(Tile.STATE_ACTIVE), anyInt());
+ }
+
+ @Test
+ public void testHandleLongClick_log() {
+ mTile.longClick(null);
+ mTile.longClick(null);
+ mTestableLooper.processAllMessages();
+ mTile.longClick(null);
+ mTestableLooper.processAllMessages();
+
+ InOrder inOrder = inOrder(mQsLogger);
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(0));
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(1));
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 0);
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 1);
+ inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(2));
+ inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 2);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 002f23a..024d3bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -23,8 +23,12 @@
import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.net.Uri
-import android.view.WindowManager
-import android.view.WindowManager.ScreenshotSource
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
@@ -43,13 +47,12 @@
@Test
fun testFullScreenshot() {
- val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
val onSavedListener = mock<Consumer<Uri>>()
val callback = mock<RequestCallback>()
val processor = RequestProcessor(controller)
- processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
- request, callback)
+ processor.processRequest(request, onSavedListener, callback)
verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
eq(onSavedListener), eq(callback))
@@ -57,13 +60,12 @@
@Test
fun testSelectedRegionScreenshot() {
- val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
val onSavedListener = mock<Consumer<Uri>>()
val callback = mock<RequestCallback>()
val processor = RequestProcessor(controller)
- processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
- request, callback)
+ processor.processRequest(request, onSavedListener, callback)
verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
eq(onSavedListener), eq(callback))
@@ -82,14 +84,13 @@
val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
- bounds, Insets.NONE, taskId, userId, topComponent)
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+ bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent)
val onSavedListener = mock<Consumer<Uri>>()
val callback = mock<RequestCallback>()
- processor.processRequest(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
- request, callback)
+ processor.processRequest(request, onSavedListener, callback)
verify(controller).handleImageAsScreenshot(
bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 360eef9..cf5fa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -19,12 +19,14 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
@@ -64,12 +66,15 @@
@Test
public void testLegacyTargetExtract() {
TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE)
- .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER)
- .addChange(TRANSIT_CLOSE, 0 /* flags */)
- .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER).build();
- // Check non-wallpaper extraction
- RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrap(combined,
- false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
+ .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER,
+ createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ .addChange(TRANSIT_CLOSE, 0 /* flags */,
+ createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
+ .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
+ // Check apps extraction
+ RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
+ mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(2, wrapped.length);
int changeLayer = -1;
int closeLayer = -1;
@@ -86,17 +91,25 @@
assertTrue(closeLayer < changeLayer);
// Check wallpaper extraction
- RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrap(combined,
+ RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(1, wallps.length);
assertTrue(wallps[0].prefixOrderIndex < closeLayer);
assertEquals(MODE_OPENING, wallps[0].mode);
+
+ // Check non-apps extraction
+ RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+ false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
+ assertEquals(1, nonApps.length);
+ assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
+ assertEquals(MODE_CHANGING, nonApps[0].mode);
}
@Test
public void testLegacyTargetWrapper() {
TransitionInfo tinfo = new TransitionInfoBuilder(TRANSIT_CLOSE)
- .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT).build();
+ .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT,
+ createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)).build();
final TransitionInfo.Change change = tinfo.getChanges().get(0);
final Rect endBounds = new Rect(40, 60, 140, 200);
change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME));
@@ -119,11 +132,12 @@
}
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
- @TransitionInfo.ChangeFlags int flags) {
+ @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {
final TransitionInfo.Change change =
new TransitionInfo.Change(null /* token */, createMockSurface(true));
change.setMode(mode);
change.setFlags(flags);
+ change.setTaskInfo(taskInfo);
mInfo.addChange(change);
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 65d0adc..c6207e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -25,7 +25,6 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.Notification;
@@ -216,41 +215,4 @@
// The entry has just been added so we should not remove immediately.
assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.getKey()));
}
-
- @Test
- public void testShouldExtendLifetime() {
- mAlertingNotificationManager.showNotification(mEntry);
-
- // While the entry is alerting, it should not be removable.
- assertTrue(mAlertingNotificationManager.shouldExtendLifetime(mEntry));
- }
-
- @Test
- public void testSetShouldManageLifetime_setShouldManage() {
- mAlertingNotificationManager.showNotification(mEntry);
-
- mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
-
- assertTrue(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
- }
-
- @Test
- public void testSetShouldManageLifetime_setShouldManageCallsRemoval() {
- mAlertingNotificationManager.showNotification(mEntry);
- mAlertingNotificationManager.setShouldManageLifetime(mEntry, true /* shouldManage */);
- if (mAlertingNotificationManager instanceof TestableAlertingNotificationManager) {
- TestableAlertingNotificationManager testableManager =
- (TestableAlertingNotificationManager) mAlertingNotificationManager;
- verify(testableManager.mLastCreatedEntry).removeAsSoonAsPossible();
- }
- }
-
- @Test
- public void testSetShouldManageLifetime_setShouldNotManage() {
- mAlertingNotificationManager.mExtendedLifetimeAlertEntries.add(mEntry);
-
- mAlertingNotificationManager.setShouldManageLifetime(mEntry, false /* shouldManage */);
-
- assertFalse(mAlertingNotificationManager.mExtendedLifetimeAlertEntries.contains(mEntry));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c5beee8..22d61a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
@@ -50,7 +51,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.IActivityManager;
import android.app.Instrumentation;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyResourcesManager;
@@ -161,8 +161,6 @@
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
- private IActivityManager mIActivityManager;
- @Mock
private KeyguardBypassController mKeyguardBypassController;
@Mock
private AccessibilityManager mAccessibilityManager;
@@ -256,8 +254,7 @@
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
- mScreenLifecycle, mIActivityManager, mKeyguardBypassController,
- mAccessibilityManager);
+ mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager);
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -974,11 +971,11 @@
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
- // THEN 'face unlocked. press unlock icon to open' message shows
- String pressToOpen = mContext.getString(R.string.keyguard_face_successful_unlock_press);
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
-
- assertThat(mTextView.getText()).isNotEqualTo(pressToOpen);
+ // THEN 'face unlocked' then 'press unlock icon to open' message show
+ String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+ String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
}
@@ -999,10 +996,11 @@
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
- // THEN show 'face unlocked. swipe up to open' message
- String faceUnlockedSwipeToOpen =
- mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+ // THEN show 'face unlocked' and 'swipe up to open' messages
+ String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+ String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
}
@Test
@@ -1021,10 +1019,11 @@
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
- // THEN show 'swipe up to open' message
- String faceUnlockedSwipeToOpen =
- mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+ // THEN show 'face unlocked' and 'swipe up to open' messages
+ String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+ String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
}
@Test
@@ -1042,10 +1041,11 @@
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
- // THEN show 'swipe up to open' message
- String faceUnlockedSwipeToOpen =
- mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+ // THEN show 'face unlocked' and 'swipe up to open' messages
+ String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+ String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 34d13c7..6e29669 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -161,10 +161,8 @@
smartReplyController,
visibilityProvider,
notificationEntryManager,
- rebuilder,
centralSurfacesOptionalLazy,
statusBarStateController,
- mainHandler,
remoteInputUriController,
clickNotifier,
actionClickLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
deleted file mode 100644
index 842f057..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.ArraySet;
-
-import androidx.annotation.NonNull;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.RowInflaterTask;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Unit tests for {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class NotificationEntryManagerTest extends SysuiTestCase {
- private static final String TEST_PACKAGE_NAME = "test";
- private static final int TEST_UID = 0;
-
- @Mock private NotificationPresenter mPresenter;
- @Mock private KeyguardEnvironment mEnvironment;
- @Mock private ExpandableNotificationRow mRow;
- @Mock private NotificationEntryListener mEntryListener;
- @Mock private NotifCollectionListener mNotifCollectionListener;
- @Mock private NotificationRemoveInterceptor mRemoveInterceptor;
- @Mock private HeadsUpManager mHeadsUpManager;
- @Mock private RankingMap mRankingMap;
- @Mock private NotificationGroupManagerLegacy mGroupManager;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private RowInflaterTask mAsyncInflationTask;
- @Mock private NotificationEntryManagerLogger mLogger;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private LeakDetector mLeakDetector;
- @Mock private NotificationMediaManager mNotificationMediaManager;
- @Mock private NotificationRowBinder mNotificationRowBinder;
- @Mock private NotificationListener mNotificationListener;
- @Mock private IStatusBarService mStatusBarService;
-
- private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
-
- private int mId;
- private NotificationEntry mEntry;
- private DismissedByUserStats mStats;
- private StatusBarNotification mSbn;
- private NotificationEntryManager mEntryManager;
-
- private void setUserSentiment(String key, int sentiment) {
- doAnswer(invocationOnMock -> {
- Ranking ranking = (Ranking)
- invocationOnMock.getArguments()[1];
- ranking.populate(
- key,
- 0,
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null, null,
- null, null, null, true, sentiment, false, -1, false, null, null, false, false,
- false, null, 0, false);
- return true;
- }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
- }
-
- private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
- doAnswer(invocationOnMock -> {
- Ranking ranking = (Ranking)
- invocationOnMock.getArguments()[1];
- ranking.populate(
- key,
- 0,
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null, null,
- null, null, null, true,
- Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
- false, smartActions, null, false, false, false, null, 0, false);
- return true;
- }).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(SmartReplyController.class);
-
- allowTestableLooperAsMainThread();
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
- Handler.createAsync(TestableLooper.get(this).getLooper()));
-
- mEntry = createNotification();
- mStats = defaultStats(mEntry);
- mSbn = mEntry.getSbn();
-
- mEntryManager = new NotificationEntryManager(
- mLogger,
- mGroupManager,
- mNotifPipelineFlags,
- () -> mNotificationRowBinder,
- () -> mRemoteInputManager,
- mLeakDetector,
- mStatusBarService,
- mock(DumpManager.class),
- mBgExecutor
- );
- mEntryManager.initialize(
- mNotificationListener,
- new NotificationRankingManager(
- () -> mNotificationMediaManager,
- mGroupManager,
- mHeadsUpManager,
- mock(NotificationFilter.class),
- mLogger,
- mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class),
- mock(HighPriorityProvider.class),
- mEnvironment));
- mEntryManager.addNotificationEntryListener(mEntryListener);
- mEntryManager.addCollectionListener(mNotifCollectionListener);
- mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
-
- setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
- }
-
- @Test
- public void testAddNotification_noDuplicateEntriesCreated() {
- // GIVEN a notification has been added
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- // WHEN the same notification is added multiple times before the previous entry (with
- // the same key) didn't finish inflating
- mEntryManager.addNotification(mSbn, mRankingMap);
- mEntryManager.addNotification(mSbn, mRankingMap);
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- // THEN getAllNotifs() only contains exactly one notification with this key
- int count = 0;
- for (NotificationEntry entry : mEntryManager.getAllNotifs()) {
- if (entry.getKey().equals(mSbn.getKey())) {
- count++;
- }
- }
- assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. "
- + "Instead there are " + count, 1, count);
- }
-
- @Test
- public void testAddNotification_setsUserSentiment() {
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- assertEquals(entry.getUserSentiment(), Ranking.USER_SENTIMENT_NEUTRAL);
- }
-
- @Test
- public void testUpdateNotification_prePostEntryOrder() throws Exception {
- TestableLooper.get(this).processAllMessages();
-
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- // Ensure that update callbacks happen in correct order
- InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
- order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(mEntryListener).onPostEntryUpdated(mEntry);
- }
-
- @Test
- public void testRemoveNotification() {
- mEntry.setRow(mRow);
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- verify(mEntryListener).onEntryRemoved(
- argThat(matchEntryOnKey()), any(),
- eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
- verify(mRow).setRemoved();
-
- assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- }
-
- @Test
- public void testRemoveUninflatedNotification_removesNotificationFromAllNotifsList() {
- // GIVEN an uninflated entry is added
- mEntryManager.addNotification(mSbn, mRankingMap);
- assertTrue(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
-
- // WHEN the uninflated entry is removed
- mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
- UNDEFINED_DISMISS_REASON);
-
- // THEN the entry is still removed from the allNotifications list
- assertFalse(entriesContainKey(mEntryManager.getAllNotifs(), mSbn.getKey()));
- }
-
- @Test
- public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
- // GIVEN an entry manager with a notification
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- // GIVEN interceptor that doesn't intercept
- when(mRemoveInterceptor.onNotificationRemoveRequested(
- eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
- .thenReturn(false);
-
- // WHEN the notification entry is removed
- mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
-
- // THEN notification removal is sent to the server
- FakeExecutor.exhaustExecutors(mBgExecutor);
- verify(mStatusBarService).onNotificationClear(
- mSbn.getPackageName(),
- mSbn.getUser().getIdentifier(),
- mSbn.getKey(),
- mStats.dismissalSurface,
- mStats.dismissalSentiment,
- mStats.notificationVisibility);
- verifyNoMoreInteractions(mStatusBarService);
- }
-
- @Test
- public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
-
- mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
-
- verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()), any(),
- eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
- }
-
- /** Regression test for b/201097913. */
- @Test
- public void testRemoveNotification_whilePending_onlyCollectionListenerNotified() {
- // Add and then remove a pending entry (entry that hasn't been inflated).
- mEntryManager.addNotification(mSbn, mRankingMap);
- mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // Verify that only the listener for the NEW pipeline is notified.
- // Old pipeline:
- verify(mEntryListener, never()).onEntryRemoved(
- argThat(matchEntryOnKey()), any(), anyBoolean(), anyInt());
- // New pipeline:
- verify(mNotifCollectionListener).onEntryRemoved(
- argThat(matchEntryOnKey()), anyInt());
- }
-
- @Test
- public void testUpdateNotificationRanking_noChange() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(mRow);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), null);
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertThat(mEntry.getSmartActions()).isEmpty();
- }
-
- @Test
- public void testUpdateNotificationRanking_pendingNotification() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(null);
- mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- }
-
- @Test
- public void testUpdatePendingNotification_rankingUpdated() {
- // GIVEN a notification with ranking is pending
- final Ranking originalRanking = mEntry.getRanking();
- mEntryManager.mPendingNotifications.put(mEntry.getKey(), mEntry);
-
- // WHEN the same notification has been updated with a new ranking
- final int newRank = 2345;
- doAnswer(invocationOnMock -> {
- Ranking ranking = (Ranking)
- invocationOnMock.getArguments()[1];
- ranking.populate(
- mEntry.getKey(),
- newRank, /* this changed!! */
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null, null,
- null, null, null, true,
- Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
- false, null, null, false, false, false, null, 0, false);
- return true;
- }).when(mRankingMap).getRanking(eq(mEntry.getKey()), any(Ranking.class));
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- // THEN ranking for the entry has been updated with new ranking
- assertEquals(newRank, mEntry.getRanking().getRank());
- }
-
- @Test
- public void testNotifyChannelModified_notifiesListeners() {
- NotificationChannel channel = mock(NotificationChannel.class);
- String pkg = "PKG";
- mEntryManager.notifyChannelModified(pkg, UserHandle.CURRENT, channel,
- NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
- verify(mNotifCollectionListener).onNotificationChannelModified(eq(pkg),
- eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
- verify(mEntryListener).onNotificationChannelModified(eq(pkg),
- eq(UserHandle.CURRENT), eq(channel), eq(NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
- }
-
- @Test
- public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
- // GIVEN an entry manager with a notification
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- // GIVEN a lifetime extender that always tries to extend lifetime
- NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
- when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
- mEntryManager.addNotificationLifetimeExtender(extender);
-
- // WHEN the notification is removed
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // THEN the extender is asked to manage the lifetime
- verify(extender).setShouldManageLifetime(mEntry, true);
- // THEN the notification is retained
- assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener, never()).onEntryRemoved(
- argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
- }
-
- @Test
- public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() {
- // GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.addActiveNotificationForTest(mEntry);
- final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
- mEntryManager.addNotificationLifetimeExtender(extender);
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- assertTrue(extender.isManaging(mEntry.getKey()));
-
- // WHEN the extender finishes its extension
- extender.setExtendLifetimes(false);
- extender.getCallback().onSafeToRemove(mEntry.getKey());
-
- // THEN the notification is removed
- assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener).onEntryRemoved(
- argThat(matchEntryOnKey()), any(), eq(false), eq(UNDEFINED_DISMISS_REASON));
- }
-
- @Test
- public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
- // GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.addActiveNotificationForTest(mEntry);
- NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
- when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
- mEntryManager.addNotificationLifetimeExtender(extender);
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // WHEN the notification is updated
- mEntryManager.updateNotification(mEntry.getSbn(), mRankingMap);
-
- // THEN the lifetime extension is canceled
- verify(extender).setShouldManageLifetime(mEntry, false);
- }
-
- @Test
- public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
- // GIVEN an entry manager with a notification
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- // GIVEN two lifetime extenders, the first which never extends and the second which
- // always extends
- NotificationLifetimeExtender extender1 = mock(NotificationLifetimeExtender.class);
- when(extender1.shouldExtendLifetime(mEntry)).thenReturn(false);
- NotificationLifetimeExtender extender2 = mock(NotificationLifetimeExtender.class);
- when(extender2.shouldExtendLifetime(mEntry)).thenReturn(true);
- mEntryManager.addNotificationLifetimeExtender(extender1);
- mEntryManager.addNotificationLifetimeExtender(extender2);
-
- // GIVEN a notification was lifetime-extended and extender2 is managing it
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(extender1, never()).setShouldManageLifetime(mEntry, true);
- verify(extender2).setShouldManageLifetime(mEntry, true);
-
- // WHEN the extender1 changes its mind and wants to extend the lifetime of the notif
- when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true);
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // THEN extender2 stops managing the notif and extender1 starts managing it
- verify(extender1).setShouldManageLifetime(mEntry, true);
- verify(extender2).setShouldManageLifetime(mEntry, false);
- }
-
- /**
- * Ensure that calling NotificationEntryManager.performRemoveNotification() doesn't crash when
- * given a notification that has already been removed from NotificationData.
- */
- @Test
- public void testPerformRemoveNotification_removedEntry() {
- mEntryManager.removeNotification(mSbn.getKey(), null, 0);
- mEntryManager.performRemoveNotification(mSbn, mock(DismissedByUserStats.class),
- REASON_CANCEL);
- }
-
- @Test
- public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
- // GIVEN an entry manager with a notification
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- // GIVEN interceptor that intercepts that entry
- when(mRemoveInterceptor.onNotificationRemoveRequested(
- eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
- .thenReturn(true);
-
- // WHEN the notification is removed
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // THEN the interceptor intercepts & the entry is not removed & no listeners are called
- assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener, never()).onEntryRemoved(argThat(matchEntryOnKey()),
- any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
- }
-
- @Test
- public void testRemoveInterceptor_notInterceptedGetsRemoved() {
- // GIVEN an entry manager with a notification
- mEntryManager.addActiveNotificationForTest(mEntry);
-
- // GIVEN interceptor that doesn't intercept
- when(mRemoveInterceptor.onNotificationRemoveRequested(
- eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
- .thenReturn(false);
-
- // WHEN the notification is removed
- mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
-
- // THEN the interceptor intercepts & the entry is not removed & no listeners are called
- assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
- verify(mEntryListener, atLeastOnce()).onEntryRemoved(argThat(matchEntryOnKey()),
- any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
- }
-
- /* Tests annexed from NotificationDataTest go here */
-
- @Test
- public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
- Notification.Builder n = new Notification.Builder(mContext, "di")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- NotificationEntry e2 = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setId(mId++)
- .setNotification(n.build())
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
- .build();
-
- mEntryManager.addActiveNotificationForTest(mEntry);
- mEntryManager.addActiveNotificationForTest(e2);
-
- when(mEnvironment.isNotificationForCurrentProfiles(mEntry.getSbn())).thenReturn(false);
- when(mEnvironment.isNotificationForCurrentProfiles(e2.getSbn())).thenReturn(true);
-
- List<NotificationEntry> result = mEntryManager.getActiveNotificationsForCurrentUser();
- assertEquals(result.size(), 1);
- junit.framework.Assert.assertEquals(result.get(0), e2);
- }
-
- /* End annex */
-
- private boolean entriesContainKey(Collection<NotificationEntry> entries, String key) {
- for (NotificationEntry entry : entries) {
- if (entry.getSbn().getKey().equals(key)) {
- return true;
- }
- }
- return false;
- }
-
- private Notification.Action createAction() {
- return new Notification.Action.Builder(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
- "action",
- PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"),
- PendingIntent.FLAG_IMMUTABLE)).build();
- }
-
- private ArgumentMatcher<NotificationEntry> matchEntryOnKey() {
- return e -> e.getKey().equals(mEntry.getKey());
- }
-
- private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender {
- private NotificationSafeToRemoveCallback mCallback;
- private boolean mExtendLifetimes = true;
- private Set<String> mManagedNotifs = new ArraySet<>();
-
- @Override
- public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return mExtendLifetimes;
- }
-
- @Override
- public void setShouldManageLifetime(
- @NonNull NotificationEntry entry,
- boolean shouldManage) {
- final boolean hasEntry = mManagedNotifs.contains(entry.getKey());
- if (shouldManage) {
- if (hasEntry) {
- throw new RuntimeException("Already managing this entry: " + entry.getKey());
- }
- mManagedNotifs.add(entry.getKey());
- } else {
- if (!hasEntry) {
- throw new RuntimeException("Not managing this entry: " + entry.getKey());
- }
- mManagedNotifs.remove(entry.getKey());
- }
- }
-
- public void setExtendLifetimes(boolean extendLifetimes) {
- mExtendLifetimes = extendLifetimes;
- }
-
- public NotificationSafeToRemoveCallback getCallback() {
- return mCallback;
- }
-
- public boolean isManaging(String notificationKey) {
- return mManagedNotifs.contains(notificationKey);
- }
- }
-
- private NotificationEntry createNotification() {
- Notification.Builder n = new Notification.Builder(mContext, "id")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- return new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setId(mId++)
- .setNotification(n.build())
- .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- }
-
- private static DismissedByUserStats defaultStats(NotificationEntry entry) {
- return new DismissedByUserStats(
- DISMISSAL_SHADE,
- DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
deleted file mode 100644
index 2cacaf7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.app.Notification;
-import android.app.Notification.MediaStyle;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.annotation.NonNull;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationFilterTest extends SysuiTestCase {
-
- private static final int UID_NORMAL = 123;
- private static final int UID_ALLOW_DURING_SETUP = 456;
- private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
-
- private final StatusBarNotification mMockStatusBarNotification =
- mock(StatusBarNotification.class);
-
- @Mock
- DebugModeFilterProvider mDebugModeFilterProvider;
- @Mock
- StatusBarStateController mStatusBarStateController;
- @Mock
- KeyguardEnvironment mEnvironment;
- @Mock
- ForegroundServiceController mFsc;
- @Mock
- NotificationLockscreenUserManager mUserManager;
- @Mock
- MediaFeatureFlag mMediaFeatureFlag;
-
- private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
-
- private NotificationFilter mNotificationFilter;
- private ExpandableNotificationRow mRow;
- private NotificationEntry mMediaEntry;
- private MediaSession mMediaSession;
-
- @Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
- MockitoAnnotations.initMocks(this);
- when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
-
- mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION");
- NotificationEntryBuilder builder = new NotificationEntryBuilder();
- builder.modifyNotification(mContext).setStyle(
- new MediaStyle().setMediaSession(mMediaSession.getSessionToken()));
- mMediaEntry = builder.build();
-
- when(mMockPackageManager.checkUidPermission(
- eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
- eq(UID_NORMAL)))
- .thenReturn(PackageManager.PERMISSION_DENIED);
- when(mMockPackageManager.checkUidPermission(
- eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
- eq(UID_ALLOW_DURING_SETUP)))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
- mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
- mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
- new NotificationGroupManagerLegacy(
- mock(StatusBarStateController.class),
- () -> mock(PeopleNotificationIdentifier.class),
- Optional.of(mock(Bubbles.class)),
- mock(DumpManager.class)));
- mDependency.injectMockDependency(ShadeController.class);
- mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
- mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
- when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
- NotificationTestHelper testHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = testHelper.createRow();
- mNotificationFilter = newNotificationFilter();
- }
-
- @NonNull
- private NotificationFilter newNotificationFilter() {
- return new NotificationFilter(
- mDebugModeFilterProvider,
- mStatusBarStateController,
- mEnvironment,
- mFsc,
- mUserManager,
- mMediaFeatureFlag);
- }
-
- @After
- public void tearDown() {
- mMediaSession.release();
- }
-
- @Test
- @UiThreadTest
- public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() {
- initStatusBarNotification(false);
- when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
-
- assertFalse(
- NotificationFilter.showNotificationEvenIfUnprovisioned(
- mMockPackageManager,
- mMockStatusBarNotification));
- }
-
- @Test
- @UiThreadTest
- public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() {
- initStatusBarNotification(true);
-
- assertFalse(
- NotificationFilter.showNotificationEvenIfUnprovisioned(
- mMockPackageManager,
- mMockStatusBarNotification));
- }
-
- @Test
- @UiThreadTest
- public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() {
- initStatusBarNotification(true);
- when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
-
- assertTrue(
- NotificationFilter.showNotificationEvenIfUnprovisioned(
- mMockPackageManager,
- mMockStatusBarNotification));
- }
-
- @Test
- public void testShouldFilterHiddenNotifications() {
- initStatusBarNotification(false);
- // setup
- when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
-
- // test should filter out hidden notifications:
- // hidden
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSuspended(true)
- .build();
-
- assertTrue(mNotificationFilter.shouldFilterOut(entry));
-
- // not hidden
- entry = new NotificationEntryBuilder()
- .setSuspended(false)
- .build();
- assertFalse(mNotificationFilter.shouldFilterOut(entry));
- }
-
- @Test
- public void shouldFilterOtherNotificationWhenDisabled() {
- // GIVEN that the media feature is disabled
- when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
- NotificationFilter filter = newNotificationFilter();
- // WHEN the media filter is asked about an entry
- NotificationEntry otherEntry = new NotificationEntryBuilder().build();
- final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
- // THEN it shouldn't be filtered
- assertFalse(shouldFilter);
- }
-
- @Test
- public void shouldFilterOtherNotificationWhenEnabled() {
- // GIVEN that the media feature is enabled
- when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
- NotificationFilter filter = newNotificationFilter();
- // WHEN the media filter is asked about an entry
- NotificationEntry otherEntry = new NotificationEntryBuilder().build();
- final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
- // THEN it shouldn't be filtered
- assertFalse(shouldFilter);
- }
-
- @Test
- public void shouldFilterMediaNotificationWhenDisabled() {
- // GIVEN that the media feature is disabled
- when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
- NotificationFilter filter = newNotificationFilter();
- // WHEN the media filter is asked about a media entry
- final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
- // THEN it shouldn't be filtered
- assertFalse(shouldFilter);
- }
-
- @Test
- public void shouldFilterMediaNotificationWhenEnabled() {
- // GIVEN that the media feature is enabled
- when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
- NotificationFilter filter = newNotificationFilter();
- // WHEN the media filter is asked about a media entry
- final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
- // THEN it should be filtered
- assertTrue(shouldFilter);
- }
-
- private void initStatusBarNotification(boolean allowDuringSetup) {
- Bundle bundle = new Bundle();
- bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
- Notification notification = new Notification.Builder(mContext, "test")
- .addExtras(bundle)
- .build();
- when(mMockStatusBarNotification.getNotification()).thenReturn(notification);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
deleted file mode 100644
index 58abbf2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class VisualStabilityManagerTest extends SysuiTestCase {
-
- private TestableLooper mTestableLooper;
-
- private VisualStabilityManager mVisualStabilityManager;
- private VisualStabilityProvider mVisualStabilityProvider = mock(VisualStabilityProvider.class);
- private VisualStabilityManager.Callback mCallback = mock(VisualStabilityManager.Callback.class);
- private VisibilityLocationProvider mLocationProvider = mock(VisibilityLocationProvider.class);
- private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
- private NotificationEntry mEntry;
-
- private StatusBarStateController.StateListener mStatusBarStateListener;
- private WakefulnessLifecycle.Observer mWakefulnessObserver;
-
- @Before
- public void setUp() {
- StatusBarStateController statusBarStateController = mock(StatusBarStateController.class);
- WakefulnessLifecycle wakefulnessLifecycle = mock(WakefulnessLifecycle.class);
-
- mTestableLooper = TestableLooper.get(this);
- mVisualStabilityManager = new VisualStabilityManager(
- mock(NotificationEntryManager.class),
- mVisualStabilityProvider,
- new Handler(mTestableLooper.getLooper()),
- statusBarStateController,
- wakefulnessLifecycle,
- mock(DumpManager.class));
-
- mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
- mEntry = new NotificationEntryBuilder().build();
- mEntry.setRow(mRow);
-
- when(mRow.getEntry()).thenReturn(mEntry);
-
- ArgumentCaptor<StatusBarStateController.StateListener> stateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- verify(statusBarStateController).addCallback(stateListenerCaptor.capture());
- mStatusBarStateListener = stateListenerCaptor.getValue();
-
- ArgumentCaptor<WakefulnessLifecycle.Observer> wakefulnessObserverCaptor =
- ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
- verify(wakefulnessLifecycle).addObserver(wakefulnessObserverCaptor.capture());
- mWakefulnessObserver = wakefulnessObserverCaptor.getValue();
- }
-
- @Test
- public void testPanelExpansion() {
- setPanelExpanded(true);
- setScreenOn(true);
- assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
- setPanelExpanded(false);
- assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testScreenOn() {
- setPanelExpanded(true);
- setScreenOn(true);
- assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
- setScreenOn(false);
- assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testReorderingAllowedChangesScreenOn() {
- setPanelExpanded(true);
- setScreenOn(true);
- assertFalse(mVisualStabilityManager.isReorderingAllowed());
- setScreenOn(false);
- assertTrue(mVisualStabilityManager.isReorderingAllowed());
- }
-
- @Test
- public void testReorderingAllowedChangesPanel() {
- setPanelExpanded(true);
- setScreenOn(true);
- assertFalse(mVisualStabilityManager.isReorderingAllowed());
- setPanelExpanded(false);
- assertTrue(mVisualStabilityManager.isReorderingAllowed());
- }
-
- @Test
- public void testCallBackCalledScreenOn() {
- setPanelExpanded(true);
- setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
- setScreenOn(false);
- verify(mCallback).onChangeAllowed();
- }
-
- @Test
- public void testCallBackCalledPanelExpanded() {
- setPanelExpanded(true);
- setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
- setPanelExpanded(false);
- verify(mCallback).onChangeAllowed();
- }
-
- @Test
- public void testCallBackExactlyOnce() {
- setPanelExpanded(true);
- setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
- setScreenOn(false);
- setScreenOn(true);
- setScreenOn(false);
- verify(mCallback).onChangeAllowed();
- }
-
- @Test
- public void testCallBackCalledContinuouslyWhenRequested() {
- setPanelExpanded(true);
- setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true /* persistent */);
- setScreenOn(false);
- setScreenOn(true);
- setScreenOn(false);
- verify(mCallback, times(2)).onChangeAllowed();
- }
-
- @Test
- public void testAddedCanReorder() {
- setPanelExpanded(true);
- setScreenOn(true);
- mVisualStabilityManager.notifyViewAddition(mRow);
- assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testReorderingVisibleHeadsUpNotAllowed() {
- setPanelExpanded(true);
- setScreenOn(true);
- when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(true);
- mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
- assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testReorderingVisibleHeadsUpAllowed() {
- setPanelExpanded(true);
- setScreenOn(true);
- when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
- mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
- assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testReorderingVisibleHeadsUpAllowedOnce() {
- setPanelExpanded(true);
- setScreenOn(true);
- when(mLocationProvider.isInVisibleLocation(any(NotificationEntry.class))).thenReturn(false);
- mVisualStabilityManager.onHeadsUpStateChanged(mEntry, true);
- mVisualStabilityManager.onReorderingFinished();
- assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testPulsing() {
- setPulsing(true);
- assertFalse(mVisualStabilityManager.canReorderNotification(mRow));
- setPulsing(false);
- assertTrue(mVisualStabilityManager.canReorderNotification(mRow));
- }
-
- @Test
- public void testReorderingAllowedChanges_Pulsing() {
- setPulsing(true);
- assertFalse(mVisualStabilityManager.isReorderingAllowed());
- setPulsing(false);
- assertTrue(mVisualStabilityManager.isReorderingAllowed());
- }
-
- @Test
- public void testCallBackCalled_Pulsing() {
- setPulsing(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
- setPulsing(false);
- verify(mCallback).onChangeAllowed();
- }
-
- @Test
- public void testTemporarilyAllowReorderingNotifiesCallbacks() {
- // GIVEN having the panel open (which would block reordering)
- setScreenOn(true);
- setPanelExpanded(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
-
- // WHEN we temprarily allow reordering
- mVisualStabilityManager.temporarilyAllowReordering();
-
- // THEN callbacks are notified that reordering is allowed
- verify(mCallback).onChangeAllowed();
- assertTrue(mVisualStabilityManager.isReorderingAllowed());
- }
-
- @Test
- public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
- // GIVEN we are in a pulsing state
- setPulsing(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
-
- // WHEN we temprarily allow reordering
- mVisualStabilityManager.temporarilyAllowReordering();
-
- // THEN reordering is still not allowed
- verify(mCallback, never()).onChangeAllowed();
- assertFalse(mVisualStabilityManager.isReorderingAllowed());
- }
-
- @Test
- public void testTemporarilyAllowReorderingExpires() {
- // GIVEN having the panel open (which would block reordering)
- setScreenOn(true);
- setPanelExpanded(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
-
- // WHEN we temprarily allow reordering and then wait until the window expires
- mVisualStabilityManager.temporarilyAllowReordering();
- assertTrue(mVisualStabilityManager.isReorderingAllowed());
- mTestableLooper.processMessages(1);
-
- // THEN reordering is no longer allowed
- assertFalse(mVisualStabilityManager.isReorderingAllowed());
- }
-
- private void setPanelExpanded(boolean expanded) {
- mStatusBarStateListener.onExpandedChanged(expanded);
- }
-
- private void setPulsing(boolean pulsing) {
- mStatusBarStateListener.onPulsingChanged(pulsing);
- }
-
- private void setScreenOn(boolean screenOn) {
- if (screenOn) {
- mWakefulnessObserver.onStartedWakingUp();
- } else {
- mWakefulnessObserver.onFinishedGoingToSleep();
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
deleted file mode 100644
index c51c628..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection
-
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
-import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_LOW
-import android.app.PendingIntent
-import android.app.Person
-import android.os.SystemClock
-import android.service.notification.NotificationListenerService.RankingMap
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger
-import com.android.systemui.statusbar.notification.NotificationFilter
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-import junit.framework.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class NotificationRankingManagerTest : SysuiTestCase() {
-
- private val lazyMedia: Lazy<NotificationMediaManager> = Lazy {
- mock(NotificationMediaManager::class.java)
- }
- private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier
- private lateinit var rankingManager: TestableNotificationRankingManager
- private lateinit var sectionsManager: NotificationSectionsFeatureManager
- private lateinit var notificationFilter: NotificationFilter
-
- @Before
- fun setup() {
- personNotificationIdentifier =
- mock(PeopleNotificationIdentifier::class.java)
- sectionsManager = mock(NotificationSectionsFeatureManager::class.java)
- notificationFilter = mock(NotificationFilter::class.java)
- rankingManager = TestableNotificationRankingManager(
- lazyMedia,
- mock(NotificationGroupManagerLegacy::class.java),
- mock(HeadsUpManager::class.java),
- notificationFilter,
- mock(NotificationEntryManagerLogger::class.java),
- sectionsManager,
- personNotificationIdentifier,
- HighPriorityProvider(
- personNotificationIdentifier,
- mock(NotificationGroupManagerLegacy::class.java)),
- mock(KeyguardEnvironment::class.java)
- )
- }
-
- @Test
- fun testSort_highPriorityTrumpsNMSRank() {
- // NMS rank says A and then B. But A is not high priority and B is, so B should sort in
- // front
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_LOW) // low priority
- .setRank(1) // NMS says rank first
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(
- Notification.Builder(mContext, "test")
- .build())
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH) // high priority
- .setRank(2) // NMS says rank second
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(
- Notification.Builder(mContext, "test")
- .build())
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- assertEquals(
- listOf(b, a),
- rankingManager.updateRanking(null, listOf(a, b), "test"))
- }
-
- @Test
- fun testSort_samePriorityUsesNMSRank() {
- // NMS rank says A and then B, and they are the same priority so use that rank
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val a = NotificationEntryBuilder()
- .setRank(1)
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(aN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val b = NotificationEntryBuilder()
- .setRank(2)
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(bN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- assertEquals(
- listOf(a, b),
- rankingManager.updateRanking(null, listOf(a, b), "test"))
- }
-
- @Test
- fun testSort_headsUp_trumpsPeople() {
- whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(aN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_IMPORTANT_PERSON)
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(bN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
- b.row = mock(ExpandableNotificationRow::class.java).also {
- whenever(it.isHeadsUp).thenReturn(true)
- }
-
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_PERSON)
-
- assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
- }
-
- @Test
- fun testSort_importantPeople() {
- whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(aN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_PERSON)
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(bN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(b))
- .thenReturn(TYPE_IMPORTANT_PERSON)
-
- whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_IMPORTANT_PERSON))
- .thenReturn(TYPE_IMPORTANT_PERSON.compareTo(TYPE_PERSON))
- whenever(personNotificationIdentifier.compareTo(TYPE_IMPORTANT_PERSON, TYPE_PERSON))
- .thenReturn(TYPE_PERSON.compareTo(TYPE_IMPORTANT_PERSON))
-
- assertEquals(
- listOf(b, a),
- rankingManager.updateRanking(null, listOf(a, b), "test"))
- }
-
- @Test
- fun testSort_fullPeople() {
- whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(aN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_PERSON)
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(bN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(b))
- .thenReturn(TYPE_FULL_PERSON)
-
- whenever(personNotificationIdentifier.compareTo(TYPE_PERSON, TYPE_FULL_PERSON))
- .thenReturn(TYPE_FULL_PERSON.compareTo(TYPE_PERSON))
- whenever(personNotificationIdentifier.compareTo(TYPE_FULL_PERSON, TYPE_PERSON))
- .thenReturn(TYPE_PERSON.compareTo(TYPE_FULL_PERSON))
-
- assertEquals(
- listOf(b, a),
- rankingManager.updateRanking(null, listOf(a, b), "test"))
- }
-
- @Test
- fun testSort_properlySetsAlertingBucket() {
- val notif = Notification.Builder(mContext, "test") .build()
-
- val e = NotificationEntryBuilder()
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(notif)
- .setUser(mContext.user)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setOverrideGroupKey("")
- .build()
-
- modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
-
- rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
- assertEquals(e.bucket, BUCKET_ALERTING)
- }
-
- @Test
- fun testSort_properlySetsSilentBucket() {
- val notif = Notification.Builder(mContext, "test") .build()
-
- val e = NotificationEntryBuilder()
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(notif)
- .setUser(mContext.user)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setOverrideGroupKey("")
- .build()
-
- modifyRanking(e).setImportance(IMPORTANCE_LOW).build()
-
- rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
- assertEquals(e.bucket, BUCKET_SILENT)
- }
-
- @Test
- fun testFilter_resetsInitalizationTime() {
- // GIVEN an entry that was initialized 1 second ago
- val notif = Notification.Builder(mContext, "test") .build()
-
- val e = NotificationEntryBuilder()
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(notif)
- .setUser(mContext.user)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setOverrideGroupKey("")
- .build()
-
- e.setInitializationTime(SystemClock.elapsedRealtime() - 1000)
- assertEquals(true, e.hasFinishedInitialization())
-
- // WHEN we update ranking and filter out the notification entry
- whenever(notificationFilter.shouldFilterOut(e)).thenReturn(true)
- rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
-
- // THEN the initialization time for the entry is reset
- assertEquals(false, e.hasFinishedInitialization())
- }
-
- @Test
- fun testSort_colorizedForegroundService() {
- whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(
- Notification.Builder(mContext, "test")
- .build())
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_DEFAULT) // high priority
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(mock(Notification::class.java).also { notif ->
- whenever(notif.isForegroundService).thenReturn(true)
- whenever(notif.isColorized).thenReturn(true)
- })
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val cN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val c = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(cN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_IMPORTANT_PERSON)
-
- assertThat(rankingManager.updateRanking(null, listOf(a, b, c), "test"))
- .containsExactly(b, c, a)
- assertThat(b.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
- }
-
- @Test
- fun testSort_importantCall() {
- whenever(sectionsManager.isFilteringEnabled()).thenReturn(true)
-
- val a = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(
- Notification.Builder(mContext, "test")
- .build())
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val b = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_DEFAULT) // high priority
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(mock(Notification::class.java).also { notif ->
- whenever(notif.isForegroundService).thenReturn(true)
- whenever(notif.isColorized).thenReturn(true)
- })
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.getUser())
- .setOverrideGroupKey("")
- .build()
-
- val cN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
- val c = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .setPkg("pkg")
- .setOpPkg("pkg")
- .setTag("tag")
- .setNotification(cN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
-
- val dN = Notification.Builder(mContext, "test")
- .setStyle(Notification.CallStyle.forOngoingCall(
- Person.Builder().setName("caller").build(),
- mock(PendingIntent::class.java)))
- .build()
- val d = NotificationEntryBuilder()
- .setImportance(IMPORTANCE_DEFAULT) // high priority
- .setPkg("pkg2")
- .setOpPkg("pkg2")
- .setTag("tag")
- .setNotification(dN)
- .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
- .setUser(mContext.user)
- .setOverrideGroupKey("")
- .build()
- whenever(personNotificationIdentifier.getPeopleNotificationType(a))
- .thenReturn(TYPE_IMPORTANT_PERSON)
-
- assertThat(rankingManager.updateRanking(null, listOf(a, b, c, d), "test"))
- .containsExactly(b, d, c, a)
- assertThat(d.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE)
- }
-
- internal class TestableNotificationRankingManager(
- mediaManager: Lazy<NotificationMediaManager>,
- groupManager: NotificationGroupManagerLegacy,
- headsUpManager: HeadsUpManager,
- filter: NotificationFilter,
- logger: NotificationEntryManagerLogger,
- sectionsFeatureManager: NotificationSectionsFeatureManager,
- peopleNotificationIdentifier: PeopleNotificationIdentifier,
- highPriorityProvider: HighPriorityProvider,
- keyguardEnvironment: KeyguardEnvironment
- ) : NotificationRankingManager(
- mediaManager,
- groupManager,
- headsUpManager,
- filter,
- logger,
- sectionsFeatureManager,
- peopleNotificationIdentifier,
- highPriorityProvider,
- keyguardEnvironment
- ) {
- fun applyTestRankingMap(r: RankingMap) {
- rankingMap = r
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index dfa38ab..9f21409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.util.time.FakeSystemClock;
@@ -1715,66 +1716,201 @@
assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());
}
- @Test(expected = IllegalStateException.class)
- public void testOutOfOrderPreGroupFilterInvalidationThrows() {
- // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
- NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null);
+ static class CountingInvalidator {
+ CountingInvalidator(Pluggable pluggableToInvalidate) {
+ mPluggableToInvalidate = pluggableToInvalidate;
+ mInvalidationCount = 0;
+ }
+
+ public void setInvalidationCount(int invalidationCount) {
+ mInvalidationCount = invalidationCount;
+ }
+
+ public void maybeInvalidate() {
+ if (mInvalidationCount > 0) {
+ mPluggableToInvalidate.invalidateList("test invalidation");
+ mInvalidationCount--;
+ }
+ }
+
+ private Pluggable mPluggableToInvalidate;
+ private int mInvalidationCount;
+
+ private static final String TAG = "ShadeListBuilderTestCountingInvalidator";
+ }
+
+ @Test
+ public void testOutOfOrderPreGroupFilterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // WHEN we try to run the pipeline and the filter is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the filter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderPrompterInvalidationThrows() {
- // GIVEN a NotifPromoter that gets invalidated during the sorting stage
+ public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPreGroupFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPreGroupFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated at least
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception is NOT thrown.
+ }
+
+ @Test
+ public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
NotifPromoter promoter = new IdPromoter(47);
- OnBeforeSortListener listener =
- (list) -> promoter.invalidateList(null);
+ CountingInvalidator invalidator = new CountingInvalidator(promoter);
+ OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(listener);
- // WHEN we try to run the pipeline and the promoter is invalidated
+ // WHEN we try to run the pipeline and the promoter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
addNotif(0, PACKAGE_1);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderComparatorInvalidationThrows() {
- // GIVEN a NotifComparator that gets invalidated during the finalizing stage
- NotifComparator comparator = new HypeComparator(PACKAGE_5);
- OnBeforeRenderListListener listener =
- (list) -> comparator.invalidateList(null);
+ public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
+ NotifPromoter promoter = new IdPromoter(47);
+ CountingInvalidator invalidator = new CountingInvalidator(promoter);
+ OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addPromoter(promoter);
+ mListBuilder.addOnBeforeSortListener(listener);
+
+ // WHEN we try to run the pipeline and the promoter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_1);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testOutOfOrderComparatorInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
+ NotifComparator comparator = new HypeComparator(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(comparator);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.setComparators(singletonList(comparator));
mListBuilder.addOnBeforeRenderListListener(listener);
- // WHEN we try to run the pipeline and the comparator is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the comparator is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderPreRenderFilterInvalidationThrows() {
- // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
- NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeRenderListListener listener = (list) -> filter.invalidateList(null);
+ public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
+ NotifComparator comparator = new HypeComparator(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(comparator);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.setComparators(singletonList(comparator));
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the comparator is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
+ }
+
+ @Test
+ public void testOutOfOrderPreRenderFilterInvalidationDoesNotThrowBeforeTooManyRuns() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeRenderListListener(listener);
- // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
- addNotif(0, PACKAGE_1);
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);
dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
- // THEN an exception is thrown
+ // THEN an exception is NOT thrown.
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
+ NotifFilter filter = new PackageFilter(PACKAGE_1);
+ CountingInvalidator invalidator = new CountingInvalidator(filter);
+ OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();
+ mListBuilder.addFinalizeFilter(filter);
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
+ // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
+ addNotif(0, PACKAGE_2);
+ invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+ dispatchBuild();
+ runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+
+ // THEN an exception IS thrown.
}
@Test
@@ -2096,6 +2232,18 @@
mPipelineChoreographer.runIfScheduled();
}
+ private void runWhileScheduledUpTo(int maxRuns) {
+ int runs = 0;
+ while (mPipelineChoreographer.isScheduled()) {
+ if (runs > maxRuns) {
+ throw new IndexOutOfBoundsException(
+ "Pipeline scheduled itself more than " + maxRuns + "times");
+ }
+ runs++;
+ mPipelineChoreographer.runIfScheduled();
+ }
+ }
+
private void verifyBuiltList(ExpectedEntry ...expectedEntries) {
try {
assertEquals(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index e00e20f..46f630b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -33,9 +33,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -57,7 +55,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -85,8 +82,6 @@
@Mock
AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock
- NotificationFilter mNotificationFilter;
- @Mock
StatusBarStateController mStatusBarStateController;
@Mock
KeyguardStateController mKeyguardStateController;
@@ -131,20 +126,10 @@
/**
* Sets up the state such that any requests to
- * {@link NotificationInterruptStateProviderImpl#canAlertCommon(NotificationEntry)} will
- * pass as long its provided NotificationEntry fulfills group suppression check.
- */
- private void ensureStateForAlertCommon() {
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
- }
-
- /**
- * Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
- ensureStateForAlertCommon();
when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -158,21 +143,10 @@
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenDozing() {
- ensureStateForAlertCommon();
-
when(mStatusBarStateController.isDozing()).thenReturn(true);
when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
}
- /**
- * Sets up the state such that any requests to
- * {@link NotificationInterruptStateProviderImpl#shouldBubbleUp(NotificationEntry)} will
- * pass as long its provided NotificationEntry fulfills importance & bubble checks.
- */
- private void ensureStateForBubbleUp() {
- ensureStateForAlertCommon();
- }
-
@Test
public void testDefaultSuppressorDoesNotSuppress() {
// GIVEN a suppressor without any overrides
@@ -201,27 +175,6 @@
}
@Test
- public void testShouldNotHeadsUpAwake_flteredOut() throws RemoteException {
- // GIVEN state for "heads up when awake" is true
- ensureStateForHeadsUpWhenAwake();
-
- // WHEN this entry should be filtered out
- NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
- when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
-
- // THEN we shouldn't heads up this entry
- assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
- }
-
- @Test
- public void testDoNotRunFilterOnNewPipeline() {
- // WHEN this entry should be filtered out
- NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
- mNotifInterruptionStateProvider.shouldHeadsUp(entry);
- verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
- }
-
- @Test
public void testShouldNotHeadsUp_suppressedForGroups() throws RemoteException {
// GIVEN state for "heads up when awake" is true
ensureStateForHeadsUpWhenAwake();
@@ -654,7 +607,6 @@
*/
@Test
public void testShouldBubbleUp() {
- ensureStateForBubbleUp();
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
}
@@ -664,7 +616,6 @@
*/
@Test
public void testShouldBubbleUp_notifInGroupWithOnlySummaryAlerts() {
- ensureStateForBubbleUp();
NotificationEntry bubble = createBubble("testgroup", GROUP_ALERT_SUMMARY);
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(bubble)).isTrue();
}
@@ -674,8 +625,6 @@
*/
@Test
public void shouldNotBubbleUp_notAllowedToBubble() {
- ensureStateForBubbleUp();
-
NotificationEntry entry = createBubble();
modifyRanking(entry)
.setCanBubble(false)
@@ -689,8 +638,6 @@
*/
@Test
public void shouldNotBubbleUp_notABubble() {
- ensureStateForBubbleUp();
-
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
modifyRanking(entry)
.setCanBubble(true)
@@ -704,8 +651,6 @@
*/
@Test
public void shouldNotBubbleUp_invalidMetadata() {
- ensureStateForBubbleUp();
-
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
modifyRanking(entry)
.setCanBubble(true)
@@ -717,8 +662,6 @@
@Test
public void shouldNotBubbleUp_suppressedInterruptions() {
- ensureStateForBubbleUp();
-
// If the notification can't heads up in general, it shouldn't bubble.
mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions);
@@ -727,8 +670,6 @@
@Test
public void shouldNotBubbleUp_filteredOut() {
- ensureStateForBubbleUp();
-
// Make canAlertCommon false by saying it's filtered out
when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
.thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index f57c409..b6a1bb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -26,7 +26,6 @@
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@@ -37,7 +36,6 @@
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -77,7 +75,6 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -88,7 +85,6 @@
import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -128,7 +124,6 @@
@Mock private AccessibilityManager mAccessibilityManager;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private INotificationManager mINotificationManager;
- @Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -160,7 +155,7 @@
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
- mShadeController, mock(DumpManager.class));
+ mShadeController);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -446,36 +441,6 @@
eq(mAssistantFeedbackController));
}
- @Test
- public void testShouldExtendLifetime() {
- NotificationGuts guts = new NotificationGuts(mContext);
- ExpandableNotificationRow row = spy(createTestNotificationRow());
- doReturn(guts).when(row).getGuts();
- NotificationEntry entry = row.getEntry();
- entry.setRow(row);
- mGutsManager.setExposedGuts(guts);
-
- assertTrue(mGutsManager.shouldExtendLifetime(entry));
- }
-
- @Test
- @Ignore
- public void testSetShouldManageLifetime_setShouldManage() {
- NotificationEntry entry = createTestNotificationRow().getEntry();
- mGutsManager.setShouldManageLifetime(entry, true /* shouldManage */);
-
- assertTrue(entry.getKey().equals(mGutsManager.mKeyToRemoveOnGutsClosed));
- }
-
- @Test
- public void testSetShouldManageLifetime_setShouldNotManage() {
- NotificationEntry entry = createTestNotificationRow().getEntry();
- mGutsManager.mKeyToRemoveOnGutsClosed = entry.getKey();
- mGutsManager.setShouldManageLifetime(entry, false /* shouldManage */);
-
- assertNull(mGutsManager.mKeyToRemoveOnGutsClosed);
- }
-
////////////////////////////////////////////////////////////////////////////////////////////////
// Utility methods:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 820dea0..c25737d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -51,7 +51,6 @@
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -62,10 +61,11 @@
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -84,7 +84,6 @@
import com.android.systemui.tests.R;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
-import com.android.wm.shell.bubbles.Bubbles;
import org.mockito.ArgumentCaptor;
@@ -112,8 +111,8 @@
private final Context mContext;
private final TestableLooper mTestLooper;
private int mId;
- private final NotificationGroupManagerLegacy mGroupMembershipManager;
- private final NotificationGroupManagerLegacy mGroupExpansionManager;
+ private final GroupMembershipManager mGroupMembershipManager;
+ private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
private HeadsUpManagerPhone mHeadsUpManager;
private final NotifBindPipeline mBindPipeline;
@@ -136,23 +135,18 @@
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogFactory.class);
mStatusBarStateController = mock(StatusBarStateController.class);
- mGroupMembershipManager = new NotificationGroupManagerLegacy(
- mStatusBarStateController,
- () -> mock(PeopleNotificationIdentifier.class),
- Optional.of((mock(Bubbles.class))),
- mock(DumpManager.class));
- mGroupExpansionManager = mGroupMembershipManager;
+ mGroupMembershipManager = mock(GroupMembershipManager.class);
+ mGroupExpansionManager = mock(GroupExpansionManager.class);
mHeadsUpManager = new HeadsUpManagerPhone(
mContext,
mock(HeadsUpManagerLogger.class),
mStatusBarStateController,
mock(KeyguardBypassController.class),
- mock(NotificationGroupManagerLegacy.class),
+ mock(GroupMembershipManager.class),
mock(VisualStabilityProvider.class),
mock(ConfigurationControllerImpl.class),
new Handler(mTestLooper.getLooper())
);
- mGroupMembershipManager.setHeadsUpManager(mHeadsUpManager);
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
@@ -528,10 +522,6 @@
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
inflateAndWait(entry);
- // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
- // the callback chain, so we need to make up for not adding it to the group manager
- // here.
- mGroupMembershipManager.onEntryAdded(entry);
return row;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ac9fcc0..9d848e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,24 +18,7 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -50,28 +33,19 @@
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
-import java.util.List;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -84,15 +58,11 @@
@Mock private ConfigurationController mConfigurationController;
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
- @Mock private NotificationRowComponent mNotificationRowComponent;
- @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
- @Mock private NotificationSectionsLogger mLogger;
@Mock private MediaContainerController mMediaContainerController;
@Mock private SectionHeaderController mIncomingHeaderController;
@Mock private SectionHeaderController mPeopleHeaderController;
@Mock private SectionHeaderController mAlertingHeaderController;
@Mock private SectionHeaderController mSilentHeaderController;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
private NotificationSectionsManager mSectionsManager;
@@ -113,22 +83,11 @@
}
return count;
});
- when(mNotificationRowComponent.getActivatableNotificationViewController())
- .thenReturn(mActivatableNotificationViewController);
- when(mMediaContainerController.getMediaContainerView())
- .thenReturn(mock(MediaContainerView.class));
- when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
- when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
- when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
- when(mSilentHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class));
mSectionsManager =
new NotificationSectionsManager(
- mStatusBarStateController,
mConfigurationController,
mKeyguardMediaController,
mSectionsFeatureManager,
- mLogger,
- mNotifPipelineFlags,
mMediaContainerController,
mIncomingHeaderController,
mPeopleHeaderController,
@@ -141,7 +100,6 @@
mSectionsManager.initialize(mNssl);
when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-
}
@Test(expected = IllegalStateException.class)
@@ -149,641 +107,4 @@
mSectionsManager.initialize(mNssl);
}
- @Test
- public void testInsertHeader() {
- // GIVEN a stack with HI and LO rows but no section headers
- setStackState(
- ALERTING,
- ALERTING,
- ALERTING,
- GENTLE);
-
- // WHEN we update the section headers
- mSectionsManager.updateSectionBoundaries();
-
- // THEN a LO section header is added
- verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
- }
-
- @Test
- public void testRemoveHeader() {
- // GIVEN a stack that originally had a header between the HI and LO sections
- setStackState(
- ALERTING,
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // WHEN the last LO row is replaced with a HI row
- setStackState(
- ALERTING,
- ALERTING,
- GENTLE_HEADER,
- ALERTING);
- clearInvocations(mNssl);
- mSectionsManager.updateSectionBoundaries();
-
- // THEN the LO section header is removed
- verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
- }
-
- @Test
- public void testDoNothingIfHeaderAlreadyRemoved() {
- // GIVEN a stack with only HI rows
- setStackState(
- ALERTING,
- ALERTING,
- ALERTING);
-
- // WHEN we update the sections headers
- mSectionsManager.updateSectionBoundaries();
-
- // THEN we don't add any section headers
- verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
- }
-
- @Test
- public void testMoveHeaderForward() {
- // GIVEN a stack that originally had a header between the HI and LO sections
- setStackState(
- ALERTING,
- ALERTING,
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // WHEN the LO section moves forward
- setStackState(
- ALERTING,
- ALERTING,
- GENTLE,
- GENTLE_HEADER,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // THEN the LO section header is also moved forward
- verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 2);
- }
-
- @Test
- public void testMoveHeaderBackward() {
- // GIVEN a stack that originally had a header between the HI and LO sections
- setStackState(
- ALERTING,
- GENTLE,
- GENTLE,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // WHEN the LO section moves backward
- setStackState(
- ALERTING,
- GENTLE_HEADER,
- ALERTING,
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // THEN the LO section header is also moved backward (with appropriate index shifting)
- verify(mNssl).changeViewPosition(mSectionsManager.getSilentHeaderView(), 3);
- }
-
- @Test
- public void testHeaderRemovedFromTransientParent() {
- // GIVEN a stack where the header is animating away
- setStackState(
- ALERTING,
- GENTLE_HEADER);
- mSectionsManager.updateSectionBoundaries();
- clearInvocations(mNssl);
-
- SectionHeaderView silentHeaderView = mSectionsManager.getSilentHeaderView();
- ViewGroup transientParent = mock(ViewGroup.class);
- when(silentHeaderView.getTransientContainer()).thenReturn(transientParent);
-
- // WHEN the LO section reappears
- setStackState(
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // THEN the header is first removed from the transient parent before being added to the
- // NSSL.
- final InOrder inOrder = inOrder(silentHeaderView, mNssl);
- inOrder.verify(silentHeaderView).removeFromTransientContainer();
- inOrder.verify(mNssl).addView(eq(silentHeaderView), eq(1));
- }
-
- @Test
- public void testHeaderNotShownOnLockscreen() {
- // GIVEN a stack of HI and LO notifs on the lockscreen
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(
- ALERTING,
- ALERTING,
- ALERTING,
- GENTLE);
-
- // WHEN we update the section headers
- mSectionsManager.updateSectionBoundaries();
-
- // Then the section header is not added
- verify(mNssl, never()).addView(eq(mSectionsManager.getSilentHeaderView()), anyInt());
- }
-
- @Test
- public void testHeaderShownWhenEnterLockscreen() {
- // GIVEN a stack of HI and LO notifs on the lockscreen
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- setStackState(
- ALERTING,
- ALERTING,
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- // WHEN we unlock
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- mSectionsManager.updateSectionBoundaries();
-
- // Then the section header is added
- verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 3);
- }
-
- @Test
- public void testHeaderHiddenWhenEnterLockscreen() {
- // GIVEN a stack of HI and LO notifs on the shade
- setStackState(
- ALERTING,
- GENTLE_HEADER,
- GENTLE);
-
- // WHEN we go back to the keyguard
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mSectionsManager.updateSectionBoundaries();
-
- // Then the section header is removed
- verify(mNssl).removeView(mSectionsManager.getSilentHeaderView());
- }
-
- @Test
- public void testPeopleFiltering_onlyAddSilentHeader() {
- enablePeopleFiltering();
-
- setStackState(
- PERSON,
- ALERTING,
- GENTLE);
- mSectionsManager.updateSectionBoundaries();
-
- verify(mNssl).addView(mSectionsManager.getSilentHeaderView(), 2);
- }
-
- @Test
- public void testPeopleFiltering_AlertingHunWhilePeopleVisible() {
- enablePeopleFiltering();
-
- setupMockStack(
- PEOPLE_HEADER,
- ALERTING,
- PERSON,
- ALERTING_HEADER,
- GENTLE_HEADER,
- GENTLE
- );
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.HEADS_UP,
- ChildType.PERSON,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
- );
- }
-
- @Test
- public void testPeopleFiltering_PersonHunWhileAlertingHunVisible() {
- enablePeopleFiltering();
-
- setupMockStack(
- PERSON,
- INCOMING_HEADER,
- ALERTING,
- PEOPLE_HEADER,
- PERSON
- );
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.HEADS_UP,
- ChildType.HEADS_UP,
- ChildType.PERSON
- );
- }
-
- @Test
- public void testPeopleFiltering_PersonHun() {
- enablePeopleFiltering();
-
- setupMockStack(
- PERSON,
- PEOPLE_HEADER,
- PERSON
- );
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.PERSON,
- ChildType.PERSON
- );
- }
-
- @Test
- public void testPeopleFiltering_AlertingHunWhilePersonHunning() {
- enablePeopleFiltering();
-
- setupMockStack(
- ALERTING,
- PERSON
- );
- mSectionsManager.updateSectionBoundaries();
- verifyMockStack(
- ChildType.HEADS_UP,
- ChildType.PERSON
- );
- }
-
- @Test
- public void testPeopleFiltering_Fsn() {
- enablePeopleFiltering();
-
- setupMockStack(
- INCOMING_HEADER,
- ALERTING,
- PEOPLE_HEADER,
- FSN,
- PERSON,
- ALERTING,
- GENTLE
- );
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.HEADS_UP,
- ChildType.FSN,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
- );
- }
-
- @Test
- public void testMediaControls_AddWhenEnterKeyguard() {
- enableMediaControls();
-
- // GIVEN a stack that doesn't include media controls
- setStackState(ALERTING, GENTLE_HEADER, GENTLE);
-
- // WHEN we go back to the keyguard
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mSectionsManager.updateSectionBoundaries();
-
- // Then the media controls are added
- verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0);
- }
-
- @Test
- public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() {
- enableMediaControls();
-
- // GIVEN a stack that doesn't include media
- setupMockStack(
- ALERTING,
- ALERTING,
- GENTLE_HEADER,
- GENTLE);
-
- // WHEN we go back to the keyguard
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.MEDIA_CONTROLS,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE);
- }
-
- @Test
- public void testRemoveNonSilentHeader() {
- enablePeopleFiltering();
- enableMediaControls();
-
- setupMockStack(
- MEDIA_CONTROLS,
- INCOMING_HEADER,
- PERSON,
- ALERTING,
- PEOPLE_HEADER,
- ALERTING_HEADER,
- ALERTING,
- ALERTING,
- GENTLE_HEADER,
- GENTLE,
- GENTLE
- );
-
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.MEDIA_CONTROLS,
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE,
- ChildType.GENTLE
- );
- }
-
- @Test
- public void testExpandIncomingSection() {
- enablePeopleFiltering();
-
- setupMockStack(
- INCOMING_HEADER,
- PERSON,
- ALERTING,
- PEOPLE_HEADER,
- ALERTING,
- PERSON,
- ALERTING_HEADER,
- ALERTING
- );
-
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.HEADS_UP,
- ChildType.HEADS_UP,
- ChildType.HEADS_UP,
- ChildType.PERSON,
- ChildType.ALERTING
- );
- }
-
- @Test
- public void testIgnoreGoneView() {
- enablePeopleFiltering();
-
- setupMockStack(
- PERSON.gone(),
- ALERTING,
- GENTLE
- );
-
- mSectionsManager.updateSectionBoundaries();
-
- verifyMockStack(
- ChildType.PERSON,
- ChildType.ALERTING,
- ChildType.GENTLE_HEADER,
- ChildType.GENTLE
- );
- }
-
- private void enablePeopleFiltering() {
- when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
- }
-
- private void enableMediaControls() {
- when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
- }
-
- private enum ChildType {
- INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP,
- FSN, PERSON, ALERTING, GENTLE, OTHER
- }
-
- private void setStackState(StackEntry... children) {
- when(mNssl.getChildCount()).thenReturn(children.length);
- for (int i = 0; i < children.length; i++) {
- View child;
- StackEntry entry = children[i];
- switch (entry.mChildType) {
- case INCOMING_HEADER:
- child = mSectionsManager.getIncomingHeaderView();
- break;
- case MEDIA_CONTROLS:
- child = mSectionsManager.getMediaControlsView();
- break;
- case PEOPLE_HEADER:
- child = mSectionsManager.getPeopleHeaderView();
- break;
- case ALERTING_HEADER:
- child = mSectionsManager.getAlertingHeaderView();
- break;
- case GENTLE_HEADER:
- child = mSectionsManager.getSilentHeaderView();
- break;
- case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
- break;
- case PERSON:
- child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
- break;
- case ALERTING:
- child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
- break;
- case GENTLE:
- child = mockNotification(BUCKET_SILENT, entry.mIsGone);
- break;
- case OTHER:
- child = mock(View.class);
- when(child.getVisibility()).thenReturn(View.VISIBLE);
- when(child.getParent()).thenReturn(mNssl);
- break;
- default:
- throw new RuntimeException("Unknown ChildType: " + children[i]);
- }
- when(mNssl.getChildAt(i)).thenReturn(child);
- when(mNssl.indexOfChild(child)).thenReturn(i);
- }
- }
-
- private View mockNotification(@PriorityBucket int bucket, boolean isGone) {
- ExpandableNotificationRow notifRow =
- mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
- when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
- when(notifRow.getParent()).thenReturn(mNssl);
-
- NotificationEntry mockEntry = mock(NotificationEntry.class);
- when(notifRow.getEntry()).thenReturn(mockEntry);
-
- int[] bucketRef = new int[] { bucket };
- when(mockEntry.getBucket()).thenAnswer(invocation -> bucketRef[0]);
- doAnswer(invocation -> {
- bucketRef[0] = invocation.getArgument(0);
- return null;
- }).when(mockEntry).setBucket(anyInt());
-
- when(notifRow.getVisibility()).thenReturn(isGone ? View.GONE : View.VISIBLE);
- return notifRow;
- }
-
- private void verifyMockStack(ChildType... expected) {
- final List<ChildType> actual = new ArrayList<>();
- int childCount = mNssl.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mNssl.getChildAt(i);
- if (child == mSectionsManager.getIncomingHeaderView()) {
- actual.add(ChildType.INCOMING_HEADER);
- continue;
- }
- if (child == mSectionsManager.getMediaControlsView()) {
- actual.add(ChildType.MEDIA_CONTROLS);
- continue;
- }
- if (child == mSectionsManager.getPeopleHeaderView()) {
- actual.add(ChildType.PEOPLE_HEADER);
- continue;
- }
- if (child == mSectionsManager.getAlertingHeaderView()) {
- actual.add(ChildType.ALERTING_HEADER);
- continue;
- }
- if (child == mSectionsManager.getSilentHeaderView()) {
- actual.add(ChildType.GENTLE_HEADER);
- continue;
- }
- if (child instanceof ExpandableNotificationRow) {
- switch (((ExpandableNotificationRow) child).getEntry().getBucket()) {
- case BUCKET_HEADS_UP:
- actual.add(ChildType.HEADS_UP);
- break;
- case BUCKET_FOREGROUND_SERVICE:
- actual.add(ChildType.FSN);
- break;
- case BUCKET_PEOPLE:
- actual.add(ChildType.PERSON);
- break;
- case BUCKET_ALERTING:
- actual.add(ChildType.ALERTING);
- break;
- case BUCKET_SILENT:
- actual.add(ChildType.GENTLE);
- break;
- default:
- actual.add(ChildType.OTHER);
- break;
- }
- continue;
- }
- actual.add(ChildType.OTHER);
- }
- assertThat(actual).containsExactly((Object[]) expected).inOrder();
- }
-
- private void setupMockStack(StackEntry... entries) {
- final List<View> children = new ArrayList<>();
- when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
- when(mNssl.getChildAt(anyInt()))
- .thenAnswer(invocation -> {
- Integer index = invocation.getArgument(0);
- if (index == null || index < 0 || index >= children.size()) {
- return null;
- }
- return children.get(index);
- });
- when(mNssl.indexOfChild(any()))
- .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
- doAnswer(invocation -> {
- View child = invocation.getArgument(0);
- int index = invocation.getArgument(1);
- children.add(index, child);
- return null;
- }).when(mNssl).addView(any(), anyInt());
- doAnswer(invocation -> {
- View child = invocation.getArgument(0);
- children.remove(child);
- return null;
- }).when(mNssl).removeView(any());
- doAnswer(invocation -> {
- View child = invocation.getArgument(0);
- int newIndex = invocation.getArgument(1);
- children.remove(child);
- children.add(newIndex, child);
- return null;
- }).when(mNssl).changeViewPosition(any(), anyInt());
- for (StackEntry entry : entries) {
- View child;
- switch (entry.mChildType) {
- case INCOMING_HEADER:
- child = mSectionsManager.getIncomingHeaderView();
- break;
- case MEDIA_CONTROLS:
- child = mSectionsManager.getMediaControlsView();
- break;
- case PEOPLE_HEADER:
- child = mSectionsManager.getPeopleHeaderView();
- break;
- case ALERTING_HEADER:
- child = mSectionsManager.getAlertingHeaderView();
- break;
- case GENTLE_HEADER:
- child = mSectionsManager.getSilentHeaderView();
- break;
- case FSN:
- child = mockNotification(BUCKET_FOREGROUND_SERVICE, entry.mIsGone);
- break;
- case PERSON:
- child = mockNotification(BUCKET_PEOPLE, entry.mIsGone);
- break;
- case ALERTING:
- child = mockNotification(BUCKET_ALERTING, entry.mIsGone);
- break;
- case GENTLE:
- child = mockNotification(BUCKET_SILENT, entry.mIsGone);
- break;
- case OTHER:
- child = mock(View.class);
- when(child.getVisibility()).thenReturn(View.VISIBLE);
- when(child.getParent()).thenReturn(mNssl);
- break;
- default:
- throw new RuntimeException("Unknown ChildType: " + entry.mChildType);
- }
- children.add(child);
- }
- }
-
- private static final StackEntry INCOMING_HEADER = new StackEntry(ChildType.INCOMING_HEADER);
- private static final StackEntry MEDIA_CONTROLS = new StackEntry(ChildType.MEDIA_CONTROLS);
- private static final StackEntry PEOPLE_HEADER = new StackEntry(ChildType.PEOPLE_HEADER);
- private static final StackEntry ALERTING_HEADER = new StackEntry(ChildType.ALERTING_HEADER);
- private static final StackEntry GENTLE_HEADER = new StackEntry(ChildType.GENTLE_HEADER);
- private static final StackEntry FSN = new StackEntry(ChildType.FSN);
- private static final StackEntry PERSON = new StackEntry(ChildType.PERSON);
- private static final StackEntry ALERTING = new StackEntry(ChildType.ALERTING);
- private static final StackEntry GENTLE = new StackEntry(ChildType.GENTLE);
-
- private static class StackEntry {
- final ChildType mChildType;
- final boolean mIsGone;
-
- StackEntry(ChildType childType) {
- this(childType, false);
- }
-
- StackEntry(ChildType childType, boolean isGone) {
- mChildType = childType;
- mIsGone = isGone;
- }
-
- public StackEntry gone() {
- return new StackEntry(mChildType, true);
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 9bcea10..1460e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -121,7 +122,8 @@
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ScrimController mScrimController;
- @Mock private NotificationGroupManagerLegacy mLegacyGroupManager;
+ @Mock private NotificationGroupManagerLegacy mGroupManagerLegacy;
+ @Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifPipeline mNotifPipeline;
@@ -174,8 +176,8 @@
mNotificationSwipeHelperBuilder,
mCentralSurfaces,
mScrimController,
- mLegacyGroupManager,
- mLegacyGroupManager,
+ mGroupManagerLegacy,
+ mGroupExpansionManager,
mSilentHeaderController,
mNotifPipeline,
mNotifCollection,
@@ -184,7 +186,6 @@
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
- mVisualStabilityManager,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 37a48937..3c22edc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -66,7 +66,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -101,8 +100,8 @@
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private SysuiStatusBarStateController mBarState;
- @Mock private NotificationGroupManagerLegacy mGroupMembershipManger;
- @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
+ @Mock private GroupMembershipManager mGroupMembershipManger;
+ @Mock private GroupExpansionManager mGroupExpansionManager;
@Mock private DumpManager mDumpManager;
@Mock private ExpandHelper mExpandHelper;
@Mock private EmptyShadeView mEmptyShadeView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 7b7f45a..a0f7087 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -418,7 +418,6 @@
wakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
- mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
mAccessibilityFloatingMenuController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index b8c8b5f..ac3d0c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -26,7 +26,6 @@
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.View;
import androidx.test.filters.SmallTest;
@@ -36,8 +35,8 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -61,19 +60,18 @@
private HeadsUpManagerPhone mHeadsUpManager;
@Mock private HeadsUpManagerLogger mHeadsUpManagerLogger;
- @Mock private NotificationGroupManagerLegacy mGroupManager;
- @Mock private View mNotificationShadeWindowView;
+ @Mock private GroupMembershipManager mGroupManager;
@Mock private VisualStabilityProvider mVSProvider;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardBypassController mBypassController;
@Mock private ConfigurationControllerImpl mConfigurationController;
private boolean mLivesPastNormalTime;
- private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
+ private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
Context context,
HeadsUpManagerLogger headsUpManagerLogger,
- NotificationGroupManagerLegacy groupManager,
+ GroupMembershipManager groupManager,
VisualStabilityProvider visualStabilityProvider,
StatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
index 44325dd..a2828d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -16,19 +16,28 @@
package com.android.systemui.statusbar.phone
+import android.app.WallpaperManager
+import android.app.WallpaperManager.OnColorsChangedListener
import android.graphics.Color
+import android.os.Handler
+import android.os.Looper
import android.testing.AndroidTestingRunner
import android.view.IWindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -38,17 +47,41 @@
private val fakeSystemClock = FakeSystemClock()
private val fakeExecutor = FakeExecutor(fakeSystemClock)
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ @get:Rule var expect: Expect = Expect.create()
@Mock private lateinit var windowManager: IWindowManager
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
private lateinit var provider: LetterboxBackgroundProvider
+ private var wallpaperColorsListener: OnColorsChangedListener? = null
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+ setUpWallpaperManager()
+ provider =
+ LetterboxBackgroundProvider(
+ windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler)
+ }
+
+ private fun setUpWallpaperManager() {
+ doAnswer { invocation ->
+ wallpaperColorsListener = invocation.arguments[0] as OnColorsChangedListener
+ return@doAnswer Unit
+ }
+ .`when`(wallpaperManager)
+ .addOnColorsChangedListener(any(), eq(mainHandler))
+ doAnswer {
+ wallpaperColorsListener = null
+ return@doAnswer Unit
+ }
+ .`when`(wallpaperManager)
+ .removeOnColorsChangedListener(any(OnColorsChangedListener::class.java))
}
@Test
@@ -76,6 +109,31 @@
}
@Test
+ fun letterboxBackgroundColor_returnsValueFromWindowManagerOnlyOnce() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+ provider.start()
+ fakeExecutor.runAllReady()
+ expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+ fakeExecutor.runAllReady()
+ expect.that(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterWallpaperChanges_returnsUpdatedColor() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.GREEN)
+ wallpaperColorsListener!!.onColorsChanged(null, 0)
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.GREEN)
+ }
+
+ @Test
fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
deleted file mode 100644
index 56dfb0c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.os.Handler;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
-import com.android.systemui.statusbar.notification.row.RowContentBindParams;
-import com.android.systemui.statusbar.notification.row.RowContentBindStage;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.HashMap;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
- @Rule public MockitoRule rule = MockitoJUnit.rule();
-
- private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
- private NotificationGroupManagerLegacy mGroupManager;
- private HeadsUpManager mHeadsUpManager;
- @Mock private NotificationEntryManager mNotificationEntryManager;
- @Mock private RowContentBindStage mBindStage;
- @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- @Mock StatusBarStateController mStatusBarStateController;
- @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
- private NotificationEntryListener mNotificationEntryListener;
- private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
- private final NotificationGroupTestHelper mGroupTestHelper =
- new NotificationGroupTestHelper(mContext);
-
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mHeadsUpManager = new HeadsUpManager(mContext, mock(HeadsUpManagerLogger.class),
- mock(Handler.class)) {};
-
- when(mNotificationEntryManager.getPendingNotificationsIterator())
- .thenReturn(mPendingEntries.values());
-
- mGroupManager = new NotificationGroupManagerLegacy(
- mStatusBarStateController,
- () -> mPeopleNotificationIdentifier,
- Optional.of(mock(Bubbles.class)),
- mock(DumpManager.class));
- mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
- mGroupManager.setHeadsUpManager(mHeadsUpManager);
-
- when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
-
- mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(
- mBindStage, mStatusBarStateController, mGroupManager);
- mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
-
- mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
- verify(mNotificationEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
- mNotificationEntryListener = mListenerCaptor.getValue();
- mHeadsUpManager.addListener(mGroupAlertTransferHelper);
- }
-
- private void mockHasHeadsUpContentView(NotificationEntry entry,
- boolean hasHeadsUpContentView) {
- RowContentBindParams params = new RowContentBindParams();
- if (hasHeadsUpContentView) {
- params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
- }
- when(mBindStage.getStageParams(eq(entry))).thenReturn(params);
- }
-
- private void mockHasHeadsUpContentView(NotificationEntry entry) {
- mockHasHeadsUpContentView(entry, true);
- }
-
- private void mockIsPriority(NotificationEntry priorityEntry) {
- when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
- .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpTransfersToChild() {
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mHeadsUpManager.showNotification(summaryEntry);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-
- mockHasHeadsUpContentView(childEntry);
-
- // Summary will be suppressed because there is only one child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // A suppressed summary should transfer its alert state to the child.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpTransfersToChildButBackAgain() {
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry2 =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Add second child notification so that summary is no longer suppressed.
- mPendingEntries.put(childEntry2.getKey(), childEntry2);
- mNotificationEntryListener.onPendingEntryAdded(childEntry2);
- mGroupManager.onEntryAdded(childEntry2);
-
- // The alert state should transfer back to the summary as there is now more than one
- // child and the summary should no longer be suppressed.
- assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpDoesntTransferBackOnDozingChanged() {
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry2 =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Set dozing to true.
- mGroupAlertTransferHelper.onDozingChanged(true);
-
- // Add second child notification so that summary is no longer suppressed.
- mPendingEntries.put(childEntry2.getKey(), childEntry2);
- mNotificationEntryListener.onPendingEntryAdded(childEntry2);
- mGroupManager.onEntryAdded(childEntry2);
-
- // Dozing changed so no reason to re-alert summary.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpTransferDoesNotAlertChildIfUninflated() {
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mHeadsUpManager.showNotification(summaryEntry);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- mockHasHeadsUpContentView(childEntry, false);
-
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Alert is immediately removed from summary, but we do not show child yet either as its
- // content is not inflated.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpTransferAlertsChildOnInflation() {
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mHeadsUpManager.showNotification(summaryEntry);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- mockHasHeadsUpContentView(childEntry, false);
-
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Child entry finishes its inflation.
- ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
- verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
- callbackCaptor.getValue().onBindFinished(childEntry);
-
- // Alert is immediately removed from summary, and we show child as its content is inflated.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
- }
-
- @Test
- public void testSuppressedSummaryHeadsUpTransferBackAbortsChildInflation() {
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- RowContentBindParams params = new RowContentBindParams();
- when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
-
- NotificationEntry childEntry2 =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Add second child notification so that summary is no longer suppressed.
- mPendingEntries.put(childEntry2.getKey(), childEntry2);
- mNotificationEntryListener.onPendingEntryAdded(childEntry2);
- mGroupManager.onEntryAdded(childEntry2);
-
- // Child entry finishes its inflation.
- ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
- verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
- callbackCaptor.getValue().onBindFinished(childEntry);
-
- assertTrue((params.getContentViews() & FLAG_CONTENT_VIEW_HEADS_UP) == 0);
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- }
-
- @Test
- public void testCleanUpPendingAlertInfo() {
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- mockHasHeadsUpContentView(childEntry, false);
-
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- mNotificationEntryListener.onEntryRemoved(
- childEntry, null, false, UNDEFINED_DISMISS_REASON);
-
- assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
- }
-
- @Test
- public void testUpdateGroupChangeDoesNotTransfer() {
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
- mockHasHeadsUpContentView(childEntry, false);
-
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Notify that entry changed groups.
- StatusBarNotification oldNotification = childEntry.getSbn();
- StatusBarNotification newSbn = spy(childEntry.getSbn().clone());
- doReturn("other_group").when(newSbn).getGroupKey();
- childEntry.setSbn(newSbn);
- mGroupManager.onEntryUpdated(childEntry, oldNotification);
-
- assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
- }
-
- @Test
- public void testUpdateChildToSummaryDoesNotTransfer() {
- final String tag = "fooTag";
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
- NotificationEntry childEntry =
- mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47, tag);
- mockHasHeadsUpContentView(childEntry, false);
-
- mHeadsUpManager.showNotification(summaryEntry);
- // Trigger a transfer of alert state from summary to child.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Update that child to a summary.
- StatusBarNotification oldNotification = childEntry.getSbn();
- childEntry.setSbn(
- mGroupTestHelper.createSummaryNotification(
- Notification.GROUP_ALERT_SUMMARY, 47, tag).getSbn());
- mGroupManager.onEntryUpdated(childEntry, oldNotification);
-
- assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
- }
-
- @Test
- public void testOverriddenSummaryHeadsUpTransfersToPriority() {
- // Creation order is oldest to newest, meaning the priority will be deemed newest
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- mockIsPriority(priorityEntry);
-
- // summary gets heads up
- mHeadsUpManager.showNotification(summaryEntry);
-
- mockHasHeadsUpContentView(summaryEntry);
- mockHasHeadsUpContentView(priorityEntry);
- mockHasHeadsUpContentView(childEntry);
-
- // Summary will have an alertOverride.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(priorityEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // An overridden summary should transfer its alert state to the priority.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
- }
-
- @Test
- public void testOverriddenSummaryHeadsUpTransferDoesNotAlertPriorityIfUninflated() {
- // Creation order is oldest to newest, meaning the priority will be deemed newest
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- mockIsPriority(priorityEntry);
-
- // summary gets heads up
- mHeadsUpManager.showNotification(summaryEntry);
-
- mockHasHeadsUpContentView(summaryEntry);
- mockHasHeadsUpContentView(priorityEntry, false);
- mockHasHeadsUpContentView(childEntry);
-
- // Summary will have an alertOverride.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(priorityEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // Alert is immediately removed from summary, but we do not show priority yet either as its
- // content is not inflated.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
- assertTrue(mGroupAlertTransferHelper.isAlertTransferPending(priorityEntry));
- }
-
- @Test
- public void testOverriddenSummaryHeadsUpTransfersToPriorityButBackAgain() {
- // Creation order is oldest to newest, meaning the child2 will ultimately be deemed newest
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry childEntry2 = mGroupTestHelper.createChildNotification(groupAlert);
- mockIsPriority(priorityEntry);
-
- // summary gets heads up
- mHeadsUpManager.showNotification(summaryEntry);
-
- mockHasHeadsUpContentView(summaryEntry);
- mockHasHeadsUpContentView(priorityEntry);
- mockHasHeadsUpContentView(childEntry);
- mockHasHeadsUpContentView(childEntry2);
-
- // Summary will have an alertOverride.
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(priorityEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- // An overridden summary should transfer its alert state to the priority.
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-
- mGroupManager.onEntryAdded(childEntry2);
-
- // An overridden summary should transfer its alert state to the priority.
- assertTrue(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry2.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
- }
-
- @Test
- public void testOverriddenSuppressedSummaryHeadsUpTransfersToChildThenToPriority() {
- // Creation order is oldest to newest, meaning the priority will ultimately be deemed newest
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- mockIsPriority(priorityEntry);
-
- // summary gets heads up
- mHeadsUpManager.showNotification(summaryEntry);
-
- mockHasHeadsUpContentView(summaryEntry);
- mockHasHeadsUpContentView(priorityEntry);
- mockHasHeadsUpContentView(childEntry);
-
- // Summary will be suppressed, and the child will receive the alert
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
-
- // Alert should be transferred "back" from the child to the priority
- mGroupManager.onEntryAdded(priorityEntry);
-
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
- }
-
- @Test
- public void testOverriddenSuppressedSummaryHeadsUpTransfersToPriorityThenToChild() {
- // Creation order is oldest to newest, meaning the child will ultimately be deemed newest
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(groupAlert);
- NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(groupAlert);
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification(groupAlert);
- mockIsPriority(priorityEntry);
-
- // summary gets heads up
- mHeadsUpManager.showNotification(summaryEntry);
-
- mockHasHeadsUpContentView(summaryEntry);
- mockHasHeadsUpContentView(priorityEntry);
- mockHasHeadsUpContentView(childEntry);
-
- // Summary will have alert override of the priority
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(priorityEntry);
-
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
-
- // Alert should be transferred "back" from the priority to the child (which is newer)
- mGroupManager.onEntryAdded(childEntry);
-
- assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
- assertTrue(mHeadsUpManager.isAlerting(childEntry.getKey()));
- assertFalse(mHeadsUpManager.isAlerting(priorityEntry.getKey()));
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
deleted file mode 100644
index d002cebe5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- private NotificationGroupManagerLegacy mGroupManager;
- private final NotificationGroupTestHelper mGroupTestHelper =
- new NotificationGroupTestHelper(mContext);
-
- @Mock
- PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- @Mock
- HeadsUpManager mHeadsUpManager;
-
- @Before
- public void setup() {
- mDependency.injectMockDependency(Bubbles.class);
- initializeGroupManager();
- }
-
- private void initializeGroupManager() {
- mGroupManager = new NotificationGroupManagerLegacy(
- mock(StatusBarStateController.class),
- () -> mPeopleNotificationIdentifier,
- Optional.of(mock(Bubbles.class)),
- mock(DumpManager.class));
- mGroupManager.setHeadsUpManager(mHeadsUpManager);
- }
-
- @Test
- public void testIsOnlyChildInGroup() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
-
- assertTrue(mGroupManager.isOnlyChildInGroup(childEntry));
- }
-
- @Test
- public void testIsChildInGroupWithSummary() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
- mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
- assertTrue(mGroupManager.isChildInGroup(childEntry));
- }
-
- @Test
- public void testIsSummaryOfGroupWithChildren() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
-
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
- mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
- assertTrue(mGroupManager.isGroupSummary(summaryEntry));
- assertEquals(summaryEntry, mGroupManager.getGroupSummary(childEntry));
- }
-
- @Test
- public void testRemoveChildFromGroupWithSummary() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
- mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
- mGroupManager.onEntryRemoved(childEntry);
-
- assertFalse(mGroupManager.isChildInGroup(childEntry));
- }
-
- @Test
- public void testRemoveSummaryFromGroupWithSummary() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
- mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
-
- mGroupManager.onEntryRemoved(summaryEntry);
-
- assertNull(mGroupManager.getGroupSummary(childEntry));
- assertFalse(mGroupManager.isGroupSummary(summaryEntry));
- }
-
- @Test
- public void testHeadsUpEntryIsIsolated() {
- NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
- NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
- mGroupManager.onEntryAdded(summaryEntry);
- mGroupManager.onEntryAdded(childEntry);
- mGroupManager.onEntryAdded(mGroupTestHelper.createChildNotification());
- when(mHeadsUpManager.isAlerting(childEntry.getKey())).thenReturn(true);
-
- mGroupManager.onHeadsUpStateChanged(childEntry, true);
-
- // Child entries that are heads upped should be considered separate groups visually even if
- // they are the same group logically
- assertEquals(childEntry, mGroupManager.getGroupSummary(childEntry));
- assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(childEntry));
- }
-
- @Test
- public void testAlertOverrideWithSiblings_0() {
- helpTestAlertOverrideWithSiblings(0);
- }
-
- @Test
- public void testAlertOverrideWithSiblings_1() {
- helpTestAlertOverrideWithSiblings(1);
- }
-
- @Test
- public void testAlertOverrideWithSiblings_2() {
- helpTestAlertOverrideWithSiblings(2);
- }
-
- @Test
- public void testAlertOverrideWithSiblings_3() {
- helpTestAlertOverrideWithSiblings(3);
- }
-
- @Test
- public void testAlertOverrideWithSiblings_9() {
- helpTestAlertOverrideWithSiblings(9);
- }
-
- /**
- * Helper for testing various sibling counts
- */
- private void helpTestAlertOverrideWithSiblings(int numSiblings) {
- helpTestAlertOverride(
- /* numSiblings */ numSiblings,
- /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* expectAlertOverride */ true);
- }
-
- @Test
- public void testAlertOverrideWithParentAlertAll() {
- // tests that summary can have GROUP_ALERT_ALL and this still works
- helpTestAlertOverride(
- /* numSiblings */ 1,
- /* summaryGroupAlert */ Notification.GROUP_ALERT_ALL,
- /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* expectAlertOverride */ true);
- }
-
- @Test
- public void testAlertOverrideWithParentAlertChild() {
- // Tests that if the summary alerts CHILDREN, there's no alertOverride
- helpTestAlertOverride(
- /* numSiblings */ 1,
- /* summaryGroupAlert */ Notification.GROUP_ALERT_CHILDREN,
- /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* expectAlertOverride */ false);
- }
-
- @Test
- public void testAlertOverrideWithChildrenAlertAll() {
- // Tests that if the children alert ALL, there's no alertOverride
- helpTestAlertOverride(
- /* numSiblings */ 1,
- /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY,
- /* priorityGroupAlert */ Notification.GROUP_ALERT_ALL,
- /* siblingGroupAlert */ Notification.GROUP_ALERT_ALL,
- /* expectAlertOverride */ false);
- }
-
- /**
- * This tests, for a group with a priority entry and the given number of siblings, that:
- * 1) the priority entry is identified as the alertOverride for the group
- * 2) the onAlertOverrideChanged method is called at that time
- * 3) when the priority entry is removed, these are reversed
- */
- private void helpTestAlertOverride(int numSiblings,
- @Notification.GroupAlertBehavior int summaryGroupAlert,
- @Notification.GroupAlertBehavior int priorityGroupAlert,
- @Notification.GroupAlertBehavior int siblingGroupAlert,
- boolean expectAlertOverride) {
- long when = 10000;
- // Create entries in an order so that the priority entry can be deemed the newest child.
- NotificationEntry[] siblings = new NotificationEntry[numSiblings];
- for (int i = 0; i < numSiblings; i++) {
- siblings[i] = mGroupTestHelper
- .createChildNotification(siblingGroupAlert, i, "sibling", ++when);
- }
- NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when);
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when);
-
- // The priority entry is an important conversation.
- when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
- .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
-
- // Register a listener so we can verify that the event is sent.
- OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
- mGroupManager.registerGroupChangeListener(groupChangeListener);
-
- // Add all the entries. The order here shouldn't matter.
- mGroupManager.onEntryAdded(summaryEntry);
- for (int i = 0; i < numSiblings; i++) {
- mGroupManager.onEntryAdded(siblings[i]);
- }
- mGroupManager.onEntryAdded(priorityEntry);
-
- if (!expectAlertOverride) {
- // Test expectation is that there will NOT be an alert, so verify that!
- NotificationGroup summaryGroup =
- mGroupManager.getGroupForSummary(summaryEntry.getSbn());
- assertNull(summaryGroup.alertOverride);
- return;
- }
- int max2Siblings = Math.min(2, numSiblings);
-
- // Verify that the summary group has the priority child as its alertOverride
- NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
- assertEquals(priorityEntry, summaryGroup.alertOverride);
- verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
- verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
- if (numSiblings > 1) {
- verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
- }
- verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
- verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
- verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
- verifyNoMoreInteractions(groupChangeListener);
-
- // Verify that only the priority notification is isolated from the group
- assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
- assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
- // Verify that the siblings are NOT isolated from the group
- for (int i = 0; i < numSiblings; i++) {
- assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
- assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
- }
-
- // Remove the priority notification to validate that it is removed as the alertOverride
- mGroupManager.onEntryRemoved(priorityEntry);
-
- // verify that the alertOverride is removed when the priority notification is
- assertNull(summaryGroup.alertOverride);
- verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
- verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
- verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
- if (numSiblings == 0) {
- verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
- }
- verifyNoMoreInteractions(groupChangeListener);
- }
-
- @Test
- public void testAlertOverrideWhenUpdatingSummaryAtEnd() {
- long when = 10000;
- int numSiblings = 2;
- int groupAlert = Notification.GROUP_ALERT_SUMMARY;
- // Create entries in an order so that the priority entry can be deemed the newest child.
- NotificationEntry[] siblings = new NotificationEntry[numSiblings];
- for (int i = 0; i < numSiblings; i++) {
- siblings[i] =
- mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when);
- }
- NotificationEntry priorityEntry =
- mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when);
- NotificationEntry summaryEntry =
- mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when);
-
- // The priority entry is an important conversation.
- when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry)))
- .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON);
-
- // Register a listener so we can verify that the event is sent.
- OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class);
- mGroupManager.registerGroupChangeListener(groupChangeListener);
-
- // Add all the entries. The order here shouldn't matter.
- mGroupManager.onEntryAdded(summaryEntry);
- for (int i = 0; i < numSiblings; i++) {
- mGroupManager.onEntryAdded(siblings[i]);
- }
- mGroupManager.onEntryAdded(priorityEntry);
-
- int max2Siblings = Math.min(2, numSiblings);
-
- // Verify that the summary group has the priority child as its alertOverride
- NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn());
- assertEquals(priorityEntry, summaryGroup.alertOverride);
- verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
- verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true);
- if (numSiblings > 1) {
- verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false);
- }
- verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey()));
- verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey()));
- verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged();
- verifyNoMoreInteractions(groupChangeListener);
-
- // Verify that only the priority notification is isolated from the group
- assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry));
- assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry));
- // Verify that the siblings are NOT isolated from the group
- for (int i = 0; i < numSiblings; i++) {
- assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i]));
- assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i]));
- }
-
- Log.d("NotificationGroupManagerLegacyTest",
- "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update summary");
-
- StatusBarNotification oldSummarySbn = mGroupTestHelper.incrementPost(summaryEntry, 10000);
- mGroupManager.onEntryUpdated(summaryEntry, oldSummarySbn);
-
- verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged();
- verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null);
- verifyNoMoreInteractions(groupChangeListener);
-
- Log.d("NotificationGroupManagerLegacyTest",
- "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update priority child");
-
- StatusBarNotification oldPrioritySbn = mGroupTestHelper.incrementPost(priorityEntry, 10000);
- mGroupManager.onEntryUpdated(priorityEntry, oldPrioritySbn);
-
- verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey()));
- verify(groupChangeListener, times(2)).onGroupCreated(any(), eq(priorityEntry.getKey()));
- verify(groupChangeListener, times(2))
- .onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry);
- verify(groupChangeListener, times(max2Siblings + 3)).onGroupsChanged();
- verifyNoMoreInteractions(groupChangeListener);
-
- Log.d("NotificationGroupManagerLegacyTest",
- "testAlertOverrideWhenUpdatingSummaryAtEnd: Done");
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c896c0a..de43a1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -178,10 +178,10 @@
bubbleSbn.getNotification().contentIntent = mContentIntent;
bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
- ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
- activeNotifications.add(mNotificationRow.getEntry());
- activeNotifications.add(mBubbleNotificationRow.getEntry());
- when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
+// ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
+// activeNotifications.add(mNotificationRow.getEntry());
+// activeNotifications.add(mBubbleNotificationRow.getEntry());
+// when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()),
anyInt())).thenReturn(mFutureDismissalRunnable);
@@ -347,9 +347,6 @@
// The content intent should NOT be sent on click.
verifyZeroInteractions(mContentIntent);
-
- // Notification should not be cancelled.
- verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
}
@Test
@@ -380,9 +377,6 @@
verify(mContentIntent).getIntent();
verify(mContentIntent).isActivity();
verifyNoMoreInteractions(mContentIntent);
-
- // Notification should not be cancelled.
- verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 23b1404..1ec4de9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -38,7 +38,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -79,7 +79,7 @@
mNotificationLockscreenUserManager);
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
- mock(NotificationGroupManagerLegacy.class), mNotificationLockscreenUserManager,
+ mock(GroupExpansionManager.class), mNotificationLockscreenUserManager,
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
mActivityStarter, mShadeController, new CommandQueue(mContext),
mock(ActionClickLogger.class), mFakeExecutor));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 87fca1f..7e07040 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -30,13 +30,13 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -331,6 +331,47 @@
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
+ @Test
+ fun screenOff_whileFolded_hingeAngleProviderRemainsOff() {
+ setFoldState(folded = true)
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOff()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
+ @Test
+ fun screenOff_whileUnfolded_hingeAngleProviderStops() {
+ setFoldState(folded = false)
+ assertThat(testHingeAngleProvider.isStarted).isTrue()
+
+ screenOnStatusProvider.notifyScreenTurningOff()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
+ @Test
+ fun screenOn_whileUnfoldedAndScreenOff_hingeAngleProviderStarted() {
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOff()
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOn()
+
+ assertThat(testHingeAngleProvider.isStarted).isTrue()
+ }
+
+ @Test
+ fun screenOn_whileFolded_hingeAngleRemainsOff() {
+ setFoldState(folded = true)
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+
+ screenOnStatusProvider.notifyScreenTurningOn()
+
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
+ }
+
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
@@ -391,6 +432,14 @@
fun notifyScreenTurnedOn() {
callbacks.forEach { it.onScreenTurnedOn() }
}
+
+ fun notifyScreenTurningOn() {
+ callbacks.forEach { it.onScreenTurningOn() }
+ }
+
+ fun notifyScreenTurningOff() {
+ callbacks.forEach { it.onScreenTurningOff() }
+ }
}
private class TestHingeAngleProvider : HingeAngleProvider {
@@ -398,11 +447,11 @@
var isStarted: Boolean = false
override fun start() {
- isStarted = true;
+ isStarted = true
}
override fun stop() {
- isStarted = false;
+ isStarted = false
}
override fun addCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
new file mode 100644
index 0000000..d886ffd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.systemui.util
+
+import android.content.SharedPreferences
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FakeSharedPreferencesTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var listener: SharedPreferences.OnSharedPreferenceChangeListener
+
+ private lateinit var sharedPreferences: SharedPreferences
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ sharedPreferences = FakeSharedPreferences()
+ }
+
+ @Test
+ fun testGetString_default() {
+ val default = "default"
+ val result = sharedPreferences.getString("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetStringSet_default() {
+ val default = setOf("one", "two")
+ val result = sharedPreferences.getStringSet("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetInt_default() {
+ val default = 10
+ val result = sharedPreferences.getInt("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetLong_default() {
+ val default = 11L
+ val result = sharedPreferences.getLong("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetFloat_default() {
+ val default = 1.3f
+ val result = sharedPreferences.getFloat("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testGetBoolean_default() {
+ val default = true
+ val result = sharedPreferences.getBoolean("key", default)
+ assertThat(result).isEqualTo(default)
+ }
+
+ @Test
+ fun testPutValuesAndRetrieve() {
+ val editor = sharedPreferences.edit()
+ val data = listOf<Data<*>>(
+ Data(
+ "keyString",
+ "value",
+ SharedPreferences.Editor::putString,
+ { getString(it, "") }
+ ),
+ Data(
+ "keyStringSet",
+ setOf("one", "two"),
+ SharedPreferences.Editor::putStringSet,
+ { getStringSet(it, emptySet()) }
+ ),
+ Data("keyInt", 10, SharedPreferences.Editor::putInt, { getInt(it, 0) }),
+ Data("keyLong", 11L, SharedPreferences.Editor::putLong, { getLong(it, 0L) }),
+ Data(
+ "keyFloat",
+ 1.3f,
+ SharedPreferences.Editor::putFloat,
+ { getFloat(it, 0f) }
+ ),
+ Data(
+ "keyBoolean",
+ true,
+ SharedPreferences.Editor::putBoolean,
+ { getBoolean(it, false) }
+ )
+ )
+
+ data.fold(editor) { ed, d ->
+ d.set(ed)
+ }
+ editor.commit()
+
+ data.forEach {
+ assertThat(it.get(sharedPreferences)).isEqualTo(it.value)
+ }
+ }
+
+ @Test
+ fun testContains() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+
+ assertThat(sharedPreferences.contains("key")).isTrue()
+ assertThat(sharedPreferences.contains("other")).isFalse()
+ }
+
+ @Test
+ fun testOverwrite() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+ sharedPreferences.edit().putInt("key", 11).commit()
+
+ assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+ }
+
+ @Test
+ fun testDeleteString() {
+ sharedPreferences.edit().putString("key", "value").commit()
+ sharedPreferences.edit().putString("key", null).commit()
+
+ assertThat(sharedPreferences.contains("key")).isFalse()
+ }
+
+ @Test
+ fun testDeleteAndReplaceString() {
+ sharedPreferences.edit().putString("key", "value").commit()
+ sharedPreferences.edit().putString("key", "other").putString("key", null).commit()
+
+ assertThat(sharedPreferences.getString("key", "")).isEqualTo("other")
+ }
+
+ @Test
+ fun testDeleteStringSet() {
+ sharedPreferences.edit().putStringSet("key", setOf("one")).commit()
+ sharedPreferences.edit().putStringSet("key", setOf("two")).commit()
+
+ assertThat(sharedPreferences.getStringSet("key", emptySet())).isEqualTo(setOf("two"))
+ }
+
+ @Test
+ fun testClear() {
+ sharedPreferences.edit().putInt("keyInt", 1).putString("keyString", "a").commit()
+ sharedPreferences.edit().clear().commit()
+
+ assertThat(sharedPreferences.contains("keyInt")).isFalse()
+ assertThat(sharedPreferences.contains("keyString")).isFalse()
+ }
+
+ @Test
+ fun testClearAndWrite() {
+ sharedPreferences.edit().putInt("key", 10).commit()
+ sharedPreferences.edit().putInt("key", 11).clear().commit()
+
+ assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11)
+ }
+
+ @Test
+ fun testListenerNotifiedOnChanges() {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+ sharedPreferences.edit().putInt("keyInt", 10).putString("keyString", "value").commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyInt")
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerNotifiedOnClear() {
+ sharedPreferences.edit().putInt("keyInt", 10).commit()
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+
+ sharedPreferences.edit().clear().commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, null)
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerNotifiedOnRemoval() {
+ sharedPreferences.edit()
+ .putString("keyString", "a")
+ .putStringSet("keySet", setOf("a"))
+ .commit()
+
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.edit().putString("keyString", null).putStringSet("keySet", null).commit()
+
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString")
+ verify(listener).onSharedPreferenceChanged(sharedPreferences, "keySet")
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun testListenerUnregistered() {
+ sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
+ sharedPreferences.edit().putInt("key", 10).commit()
+
+ verify(listener, never()).onSharedPreferenceChanged(eq(sharedPreferences), anyString())
+ }
+
+ @Test
+ fun testSharedPreferencesOnlyModifiedOnCommit() {
+ sharedPreferences.edit().putInt("key", 10)
+
+ assertThat(sharedPreferences.contains("key")).isFalse()
+ }
+
+ private data class Data<T>(
+ val key: String,
+ val value: T,
+ private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor,
+ private val getter: SharedPreferences.(String) -> T
+ ) {
+ fun set(editor: SharedPreferences.Editor): SharedPreferences.Editor {
+ return editor.setter(key, value)
+ }
+
+ fun get(sharedPreferences: SharedPreferences): T {
+ return sharedPreferences.getter(key)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
new file mode 100644
index 0000000..4a881a7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.systemui.util
+
+import android.content.SharedPreferences
+
+/**
+ * Fake [SharedPreferences] to use within tests
+ *
+ * This will act in the same way as a real one for a particular file, but will store all the
+ * data in memory in the instance.
+ *
+ * [SharedPreferences.Editor.apply] and [SharedPreferences.Editor.commit] both act in the same way,
+ * synchronously modifying the stored data. Listeners are dispatched in the same thread, also
+ * synchronously.
+ */
+class FakeSharedPreferences : SharedPreferences {
+ private val data = mutableMapOf<String, Any>()
+ private val listeners = mutableSetOf<SharedPreferences.OnSharedPreferenceChangeListener>()
+
+ override fun getAll(): Map<String, *> {
+ return data
+ }
+
+ override fun getString(key: String, defValue: String?): String? {
+ return data.getOrDefault(key, defValue) as? String?
+ }
+
+ override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
+ return data.getOrDefault(key, defValues) as? MutableSet<String>?
+ }
+
+ override fun getInt(key: String, defValue: Int): Int {
+ return data.getOrDefault(key, defValue) as Int
+ }
+
+ override fun getLong(key: String, defValue: Long): Long {
+ return data.getOrDefault(key, defValue) as Long
+ }
+
+ override fun getFloat(key: String, defValue: Float): Float {
+ return data.getOrDefault(key, defValue) as Float
+ }
+
+ override fun getBoolean(key: String, defValue: Boolean): Boolean {
+ return data.getOrDefault(key, defValue) as Boolean
+ }
+
+ override fun contains(key: String): Boolean {
+ return key in data
+ }
+
+ override fun edit(): SharedPreferences.Editor {
+ return Editor()
+ }
+
+ override fun registerOnSharedPreferenceChangeListener(
+ listener: SharedPreferences.OnSharedPreferenceChangeListener
+ ) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterOnSharedPreferenceChangeListener(
+ listener: SharedPreferences.OnSharedPreferenceChangeListener
+ ) {
+ listeners.remove(listener)
+ }
+
+ private inner class Editor : SharedPreferences.Editor {
+
+ private var clear = false
+ private val changes = mutableMapOf<String, Any>()
+ private val removals = mutableSetOf<String>()
+
+ override fun putString(key: String, value: String?): SharedPreferences.Editor {
+ if (value != null) {
+ changes[key] = value
+ } else {
+ removals.add(key)
+ }
+ return this
+ }
+
+ override fun putStringSet(
+ key: String,
+ values: MutableSet<String>?
+ ): SharedPreferences.Editor {
+ if (values != null) {
+ changes[key] = values
+ } else {
+ removals.add(key)
+ }
+ return this
+ }
+
+ override fun putInt(key: String, value: Int): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putLong(key: String, value: Long): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
+ changes[key] = value
+ return this
+ }
+
+ override fun remove(key: String): SharedPreferences.Editor {
+ removals.add(key)
+ return this
+ }
+
+ override fun clear(): SharedPreferences.Editor {
+ clear = true
+ return this
+ }
+
+ override fun commit(): Boolean {
+ if (clear) {
+ data.clear()
+ }
+ removals.forEach { data.remove(it) }
+ data.putAll(changes)
+ val keys = removals + data.keys
+ if (clear || removals.isNotEmpty() || data.isNotEmpty()) {
+ listeners.forEach { listener ->
+ if (clear) {
+ listener.onSharedPreferenceChanged(this@FakeSharedPreferences, null)
+ }
+ keys.forEach {
+ listener.onSharedPreferenceChanged(this@FakeSharedPreferences, it)
+ }
+ }
+ }
+ return true
+ }
+
+ override fun apply() {
+ commit()
+ }
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index e8038fd..19cfc80 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -65,6 +65,7 @@
private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis
private var isFolded = false
+ private var isScreenOn = false
private var isUnfoldHandled = true
override fun start() {
@@ -198,6 +199,25 @@
isUnfoldHandled = true
}
}
+
+ override fun onScreenTurningOn() {
+ isScreenOn = true
+ updateHingeAngleProviderState()
+ }
+
+ override fun onScreenTurningOff() {
+ isScreenOn = false
+ updateHingeAngleProviderState()
+ }
+ }
+
+ /** While the screen is off or the device is folded, hinge angle updates are not needed. */
+ private fun updateHingeAngleProviderState() {
+ if (isScreenOn && !isFolded) {
+ hingeAngleProvider.start()
+ } else {
+ hingeAngleProvider.stop()
+ }
}
private inner class HingeAngleListener : Consumer<Float> {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index 3fc5d61..577137c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -30,8 +30,10 @@
private val sensorListener = HingeAngleSensorListener()
private val listeners: MutableList<Consumer<Float>> = arrayListOf()
+ var started = false
override fun start() = executor.execute {
+ if (started) return@execute
Trace.beginSection("HingeSensorAngleProvider#start")
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
sensorManager.registerListener(
@@ -40,10 +42,13 @@
SensorManager.SENSOR_DELAY_FASTEST
)
Trace.endSection()
+ started = true
}
override fun stop() = executor.execute {
+ if (!started) return@execute
sensorManager.unregisterListener(sensorListener)
+ started = false
}
override fun removeCallback(listener: Consumer<Float>) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index d95e050..f09b53d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -25,5 +25,15 @@
* Called when the screen is on and ready (windows are drawn and screen blocker is removed)
*/
fun onScreenTurnedOn()
+
+ /**
+ * Called when the screen is starting to be turned off.
+ */
+ fun onScreenTurningOff()
+
+ /**
+ * Called when the screen is starting to be turned on.
+ */
+ fun onScreenTurningOn()
}
}
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 0eaf359..9bf86f5 100644
--- a/packages/VpnDialogs/res/values-es/strings.xml
+++ b/packages/VpnDialogs/res/values-es/strings.xml
@@ -19,7 +19,7 @@
<string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparece en la parte superior de la pantalla cuando se active la conexión VPN."</string>
<string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita monitorizar el tráfico de red. Acéptalo solo si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparecerá en la pantalla cuando la VPN esté activa."</string>
- <string name="legacy_title" msgid="192936250066580964">"VPN conectada"</string>
+ <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada"</string>
<string name="session" msgid="6470628549473641030">"Sesión:"</string>
<string name="duration" msgid="3584782459928719435">"Duración:"</string>
<string name="data_transmitted" msgid="7988167672982199061">"Enviado:"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index b34482f..3324c52 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -354,16 +354,24 @@
if (supportsFlagForNotImportantViews(info)) {
if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags |=
+ AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
} else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags &=
+ ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
}
}
if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
} else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
+ }
+
+ if (mAccessibilityServiceInfo.isAccessibilityTool()) {
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+ } else {
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
}
mRequestTouchExplorationMode = (info.flags
@@ -1522,9 +1530,16 @@
return false;
}
+ final boolean includeNotImportantViews = (mFetchFlags
+ & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
&& !event.isImportantForAccessibility()
- && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+ && !includeNotImportantViews) {
+ return false;
+ }
+
+ if (event.isAccessibilityDataPrivate()
+ && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6eabc98..6a6d2bb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3693,6 +3693,7 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ info.setAccessibilityTool(true);
final AccessibilityUserState userState;
synchronized (mLock) {
userState = getCurrentUserStateLocked();
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index 930f49e..a4ea698 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -16,7 +16,9 @@
package com.android.server.backup;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import android.annotation.Nullable;
@@ -166,6 +168,17 @@
onPackageEnabled(packageName);
return;
}
+ case COMPONENT_ENABLED_STATE_DEFAULT: {
+ // Package is set to its default enabled state (as specified in its manifest).
+ // Unless explicitly specified in manifest, the default enabled state
+ // is 'enabled'. Here, we assume that default state always means enabled.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package " + packageName
+ + " was put in default enabled state.");
+ }
+ onPackageEnabled(packageName);
+ return;
+ }
case COMPONENT_ENABLED_STATE_DISABLED: {
if (MORE_DEBUG) {
Slog.d(TAG, "Package " + packageName + " was disabled.");
@@ -173,6 +186,13 @@
onPackageDisabled(packageName);
return;
}
+ case COMPONENT_ENABLED_STATE_DISABLED_USER: {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package " + packageName + " was disabled by user.");
+ }
+ onPackageDisabled(packageName);
+ return;
+ }
default: {
Slog.w(TAG, "Package " + packageName + " enabled setting: " + enabled);
return;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 38275f7..1c571a7 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,7 +25,6 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
-import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
@@ -284,6 +283,7 @@
*/
@Override
public boolean isUserReadyForBackup(int userId) {
+ enforceCallingPermissionOnUserId(userId, "isUserReadyForBackup()");
return mUserServices.get(UserHandle.USER_SYSTEM) != null
&& mUserServices.get(userId) != null;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 83e3b49..4a3f682a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3047,6 +3047,10 @@
try {
mVold.createUserKey(userId, serialNumber, ephemeral);
+ // New keys are always unlocked.
+ synchronized (mLock) {
+ mLocalUnlockedUsers.append(userId);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3058,6 +3062,10 @@
try {
mVold.destroyUserKey(userId);
+ // Destroying a key also locks it.
+ synchronized (mLock) {
+ mLocalUnlockedUsers.remove(userId);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ef784bb..5393b2a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -160,6 +160,7 @@
public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
"android.hardware.biometrics.face.IFace/",
"android.hardware.biometrics.fingerprint.IFingerprint/",
+ "android.hardware.graphics.composer3.IComposer/",
"android.hardware.input.processor.IInputProcessor/",
"android.hardware.light.ILights/",
"android.hardware.power.IPower/",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e7ea903..6aa472f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -166,6 +166,7 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
@@ -5761,7 +5762,7 @@
}
void serviceTimeout(ProcessRecord proc) {
- String anrMessage = null;
+ TimeoutRecord timeoutRecord = null;
synchronized(mAm) {
if (proc.isDebugging()) {
// The app's being debugged, ignore timeout.
@@ -5796,7 +5797,8 @@
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
- anrMessage = "executing service " + timeout.shortInstanceName;
+ String anrMessage = "executing service " + timeout.shortInstanceName;
+ timeoutRecord = TimeoutRecord.forServiceExec(anrMessage);
} else {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -5806,13 +5808,15 @@
}
}
- if (anrMessage != null) {
- mAm.mAnrHelper.appNotResponding(proc, anrMessage);
+ if (timeoutRecord != null) {
+ mAm.mAnrHelper.appNotResponding(proc, timeoutRecord);
}
}
void serviceForegroundTimeout(ServiceRecord r) {
ProcessRecord app;
+ // Grab a timestamp before lock is taken.
+ long timeoutEndMs = SystemClock.uptimeMillis();
synchronized (mAm) {
if (!r.fgRequired || !r.fgWaiting || r.destroying) {
return;
@@ -5836,17 +5840,19 @@
+ "Service.startForeground(): " + r;
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
+ TimeoutRecord timeoutRecord = TimeoutRecord.forServiceStartWithEndTime(annotation,
+ timeoutEndMs);
SomeArgs args = SomeArgs.obtain();
args.arg1 = app;
- args.arg2 = annotation;
+ args.arg2 = timeoutRecord;
msg.obj = args;
mAm.mHandler.sendMessageDelayed(msg,
mAm.mConstants.mServiceStartForegroundAnrDelayMs);
}
}
- void serviceForegroundTimeoutANR(ProcessRecord app, String annotation) {
- mAm.mAnrHelper.appNotResponding(app, annotation);
+ void serviceForegroundTimeoutANR(ProcessRecord app, TimeoutRecord timeoutRecord) {
+ mAm.mAnrHelper.appNotResponding(app, timeoutRecord);
}
public void updateServiceApplicationInfoLocked(ApplicationInfo applicationInfo) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 744ec9d..e46639b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -357,6 +357,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.SomeArgs;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
import com.android.internal.policy.AttributeCache;
@@ -1700,7 +1701,7 @@
case SERVICE_FOREGROUND_TIMEOUT_ANR_MSG: {
SomeArgs args = (SomeArgs) msg.obj;
mServices.serviceForegroundTimeoutANR((ProcessRecord) args.arg1,
- (String) args.arg2);
+ (TimeoutRecord) args.arg2);
args.recycle();
} break;
case SERVICE_FOREGROUND_CRASH_MSG: {
@@ -5014,7 +5015,8 @@
hostingRecord.getType(),
hostingRecord.getName(),
shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()));
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
return true;
}
@@ -6416,6 +6418,7 @@
@Override
public void appNotResponding(final String reason) {
+ TimeoutRecord timeoutRecord = TimeoutRecord.forApp("App requested: " + reason);
final int callingPid = Binder.getCallingPid();
synchronized (mPidsSelfLocked) {
@@ -6425,7 +6428,7 @@
}
mAnrHelper.appNotResponding(app, null, app.info, null, null, false,
- "App requested: " + reason);
+ timeoutRecord);
}
}
@@ -8894,9 +8897,9 @@
InputStreamReader input = null;
try {
java.lang.Process logcat = new ProcessBuilder(
- // Time out after 10s, but kill logcat with SEGV
+ // Time out after 10s of inactivity, but kill logcat with SEGV
// so we can investigate why it didn't finish.
- "/system/bin/timeout", "-s", "SEGV", "10s",
+ "/system/bin/timeout", "-i", "-s", "SEGV", "10s",
// Merge several logcat streams, and take the last N lines.
"/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
"-b", "main", "-b", "crash", "-t", String.valueOf(lines))
@@ -14339,10 +14342,12 @@
if (oldRecord.resultTo != null) {
final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
try {
+ oldRecord.mIsReceiverAppRunning = true;
oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.userId, oldRecord.callingUid, callingUid);
+ false, false, oldRecord.userId, oldRecord.callingUid, callingUid,
+ SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
+ queue.mQueueName + "] sending broadcast result of "
@@ -14695,10 +14700,11 @@
if (!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID
&& callingUid != SYSTEM_UID) {
// If it's not debug build and not called from root/shell/system uid, reject it.
- String msg = "Permission Denial: instrumentation test "
+ final String msg = "Permission Denial: instrumentation test "
+ className + " from pid=" + callingPid + ", uid=" + callingUid
- + " not allowed because target package " + ii.targetPackage
- + " is not debuggable.";
+ + ", pkgName=" + mInternal.getPackageNameByPid(callingPid)
+ + " not allowed because it's not started from SHELL";
+ Slog.wtfQuiet(TAG, msg);
reportStartInstrumentationFailureLocked(watcher, className, msg);
throw new SecurityException(msg);
}
@@ -17168,18 +17174,19 @@
}
@Override
- public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
- return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem, reason);
+ public long inputDispatchingTimedOut(int pid, boolean aboveSystem,
+ TimeoutRecord timeoutRecord) {
+ return ActivityManagerService.this.inputDispatchingTimedOut(pid, aboveSystem,
+ timeoutRecord);
}
@Override
public boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
- boolean aboveSystem, String reason) {
+ boolean aboveSystem, TimeoutRecord timeoutRecord) {
return ActivityManagerService.this.inputDispatchingTimedOut((ProcessRecord) proc,
activityShortComponentName, aInfo, parentShortComponentName,
- (WindowProcessController) parentProc, aboveSystem, reason);
-
+ (WindowProcessController) parentProc, aboveSystem, timeoutRecord);
}
@Override
@@ -17741,7 +17748,7 @@
}
}
- long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
+ long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission " + FILTER_EVENTS);
}
@@ -17752,7 +17759,7 @@
final long timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
+ if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, timeoutRecord)) {
return 0;
}
@@ -17765,18 +17772,12 @@
*/
boolean inputDispatchingTimedOut(ProcessRecord proc, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
- WindowProcessController parentProcess, boolean aboveSystem, String reason) {
+ WindowProcessController parentProcess, boolean aboveSystem,
+ TimeoutRecord timeoutRecord) {
if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission " + FILTER_EVENTS);
}
- final String annotation;
- if (reason == null) {
- annotation = "Input dispatching timed out";
- } else {
- annotation = "Input dispatching timed out (" + reason + ")";
- }
-
if (proc != null) {
synchronized (this) {
if (proc.isDebugging()) {
@@ -17786,13 +17787,13 @@
if (proc.getActiveInstrumentation() != null) {
Bundle info = new Bundle();
info.putString("shortMsg", "keyDispatchingTimedOut");
- info.putString("longMsg", annotation);
+ info.putString("longMsg", timeoutRecord.mReason);
finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
return true;
}
}
mAnrHelper.appNotResponding(proc, activityShortComponentName, aInfo,
- parentShortComponentName, parentProcess, aboveSystem, annotation);
+ parentShortComponentName, parentProcess, aboveSystem, timeoutRecord);
}
return true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index dbabe99..4bbfa3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -51,6 +51,7 @@
import android.app.IActivityTaskManager;
import android.app.IStopUserCallback;
import android.app.IUidObserver;
+import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.ProfilerInfo;
import android.app.RemoteServiceException.CrashedByAdbException;
@@ -1951,31 +1952,36 @@
// Register switch observer.
final CountDownLatch switchLatch = new CountDownLatch(1);
- mInterface.registerUserSwitchObserver(
- new UserSwitchObserver() {
- @Override
- public void onUserSwitchComplete(int newUserId) {
- if (userId == newUserId) {
- switchLatch.countDown();
- }
- }
- }, ActivityManagerShellCommand.class.getName());
-
- // Switch.
- boolean switched = mInterface.switchUser(userId);
- if (!switched) {
- // Switching failed, don't wait for the user switch observer.
- return false;
- }
-
- // Wait.
+ final IUserSwitchObserver userSwitchObserver = new UserSwitchObserver() {
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ if (userId == newUserId) {
+ switchLatch.countDown();
+ }
+ }
+ };
try {
- switched = switchLatch.await(USER_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- getErrPrintWriter().println("Error: Thread interrupted unexpectedly.");
- }
+ mInterface.registerUserSwitchObserver(userSwitchObserver,
+ ActivityManagerShellCommand.class.getName());
- return switched;
+ // Switch.
+ boolean switched = mInterface.switchUser(userId);
+ if (!switched) {
+ // Switching failed, don't wait for the user switch observer.
+ return false;
+ }
+
+ // Wait.
+ try {
+ switched = switchLatch.await(USER_OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ getErrPrintWriter().println("Error: Thread interrupted unexpectedly.");
+ }
+
+ return switched;
+ } finally {
+ mInterface.unregisterUserSwitchObserver(userSwitchObserver);
+ }
}
int runSwitchUser(PrintWriter pw) throws RemoteException {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index e354495..f1d8353 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -24,6 +24,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.TimeoutRecord;
import com.android.server.wm.WindowProcessController;
import java.util.ArrayList;
@@ -68,15 +69,16 @@
mService = service;
}
- void appNotResponding(ProcessRecord anrProcess, String annotation) {
+ void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
- false /* aboveSystem */, annotation);
+ false /* aboveSystem */, timeoutRecord);
}
void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
- WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
+ WindowProcessController parentProcess, boolean aboveSystem,
+ TimeoutRecord timeoutRecord) {
final int incomingPid = anrProcess.mPid;
synchronized (mAnrRecords) {
if (incomingPid == 0) {
@@ -85,17 +87,19 @@
return;
}
if (mProcessingPid == incomingPid) {
- Slog.i(TAG, "Skip duplicated ANR, pid=" + incomingPid + " " + annotation);
+ Slog.i(TAG,
+ "Skip duplicated ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
return;
}
for (int i = mAnrRecords.size() - 1; i >= 0; i--) {
if (mAnrRecords.get(i).mPid == incomingPid) {
- Slog.i(TAG, "Skip queued ANR, pid=" + incomingPid + " " + annotation);
+ Slog.i(TAG,
+ "Skip queued ANR, pid=" + incomingPid + " " + timeoutRecord.mReason);
return;
}
}
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
- parentShortComponentName, parentProcess, aboveSystem, annotation));
+ parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
}
startAnrConsumerIfNeeded();
}
@@ -175,7 +179,7 @@
final int mPid;
final String mActivityShortComponentName;
final String mParentShortComponentName;
- final String mAnnotation;
+ final TimeoutRecord mTimeoutRecord;
final ApplicationInfo mAppInfo;
final WindowProcessController mParentProcess;
final boolean mAboveSystem;
@@ -183,12 +187,13 @@
AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
- WindowProcessController parentProcess, boolean aboveSystem, String annotation) {
+ WindowProcessController parentProcess, boolean aboveSystem,
+ TimeoutRecord timeoutRecord) {
mApp = anrProcess;
mPid = anrProcess.mPid;
mActivityShortComponentName = activityShortComponentName;
mParentShortComponentName = parentShortComponentName;
- mAnnotation = annotation;
+ mTimeoutRecord = timeoutRecord;
mAppInfo = aInfo;
mParentProcess = parentProcess;
mAboveSystem = aboveSystem;
@@ -196,7 +201,8 @@
void appNotResponding(boolean onlyDumpSelf) {
mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
- mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
+ mParentShortComponentName, mParentProcess, mAboveSystem,
+ mTimeoutRecord,
onlyDumpSelf);
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 768fdfd..cc2b693 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -758,6 +758,7 @@
.setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
+ .includePowerModels()
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
@@ -768,6 +769,7 @@
.includeProcessStateData()
.includeVirtualUids()
.powerProfileModeledOnly()
+ .includePowerModels()
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6d520c3..aaaacef 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -82,6 +82,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -321,7 +322,7 @@
}
private final void processCurBroadcastLocked(BroadcastRecord r,
- ProcessRecord app, int receiverType, int processTemperature) throws RemoteException {
+ ProcessRecord app) throws RemoteException {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " for app " + app);
final IApplicationThread thread = app.getThread();
@@ -367,10 +368,6 @@
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, app.uid,
- r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- ActivityManagerService.getShortAction(r.intent.getAction()),
- receiverType, processTemperature);
} finally {
if (!started) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
@@ -407,9 +404,8 @@
}
try {
mPendingBroadcast = null;
- processCurBroadcastLocked(br, app,
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD);
+ br.mIsReceiverAppRunning = false;
+ processCurBroadcastLocked(br, app);
didSomething = true;
} catch (Exception e) {
Slog.w(TAG, "Exception in new application when starting receiver "
@@ -517,6 +513,22 @@
final long finishTime = SystemClock.uptimeMillis();
final long elapsed = finishTime - r.receiverTime;
r.state = BroadcastRecord.IDLE;
+ final int curIndex = r.nextReceiver - 1;
+ if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
+ final Object curReceiver = r.receivers.get(curIndex);
+ FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
+ r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
+ ActivityManagerService.getShortAction(r.intent.getAction()),
+ curReceiver instanceof BroadcastFilter
+ ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
+ : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
+ r.mIsReceiverAppRunning
+ ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
+ : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
+ r.dispatchTime - r.enqueueTime,
+ r.receiverTime - r.dispatchTime,
+ finishTime - r.receiverTime);
+ }
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
}
@@ -640,7 +652,8 @@
void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, int sendingUser,
- int receiverUid, int callingUid) throws RemoteException {
+ int receiverUid, int callingUid, long dispatchDelay,
+ long receiveDelay) throws RemoteException {
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null) {
final IApplicationThread thread = app.getThread();
@@ -674,12 +687,15 @@
receiver.performReceive(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
}
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
- receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
- callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- ActivityManagerService.getShortAction(intent.getAction()),
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
+ if (!ordered) {
+ FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
+ receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
+ callingUid == -1 ? Process.SYSTEM_UID : callingUid,
+ ActivityManagerService.getShortAction(intent.getAction()),
+ BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
+ BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
+ dispatchDelay, receiveDelay, 0 /* finish_delay */);
+ }
}
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
@@ -983,7 +999,9 @@
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
new Intent(r.intent), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId,
- filter.receiverList.uid, r.callingUid);
+ filter.receiverList.uid, r.callingUid,
+ r.dispatchTime - r.enqueueTime,
+ r.receiverTime - r.dispatchTime);
// parallel broadcasts are fire-and-forget, not bookended by a call to
// finishReceiverLocked(), so we manage their activity-start token here
if (filter.receiverList.app != null
@@ -1166,6 +1184,7 @@
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
+ r.mIsReceiverAppRunning = true;
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -1333,10 +1352,18 @@
Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
+ r.intent.getAction() + " app=" + r.callerApp);
}
+ if (r.dispatchTime == 0) {
+ // The dispatch time here could be 0, in case it's a parallel
+ // broadcast but it has a result receiver. Set it to now.
+ r.dispatchTime = now;
+ }
+ r.mIsReceiverAppRunning = true;
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId,
- r.callingUid, r.callingUid);
+ r.callingUid, r.callingUid,
+ r.dispatchTime - r.enqueueTime,
+ now - r.dispatchTime);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
// Set this to null so that the reference
// (local and remote) isn't kept in the mBroadcastHistory.
@@ -1493,6 +1520,7 @@
"Delivering ordered ["
+ mQueueName + "] to registered "
+ filter + ": " + r);
+ r.mIsReceiverAppRunning = true;
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
if (r.receiver == null || !r.ordered) {
// The receiver has already finished, so schedule to
@@ -1856,9 +1884,8 @@
app.addPackage(info.activityInfo.packageName,
info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
maybeAddAllowBackgroundActivityStartsToken(app, r);
- processCurBroadcastLocked(r, app,
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
+ r.mIsReceiverAppRunning = true;
+ processCurBroadcastLocked(r, app);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when sending broadcast to "
@@ -1892,7 +1919,8 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction()),
+ r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
+ : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -2134,7 +2162,7 @@
}
ProcessRecord app = null;
- String anrMessage = null;
+ TimeoutRecord timeoutRecord = null;
Object curReceiver;
if (r.nextReceiver > 0) {
@@ -2159,9 +2187,10 @@
}
if (app != null) {
- anrMessage =
+ String anrMessage =
"Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs
+ "ms";
+ timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
}
if (mPendingBroadcast == r) {
@@ -2173,8 +2202,8 @@
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
- if (!debugging && anrMessage != null) {
- mService.mAnrHelper.appNotResponding(app, anrMessage);
+ if (!debugging && timeoutRecord != null) {
+ mService.mAnrHelper.appNotResponding(app, timeoutRecord);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ae91d75..ce4528b 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -135,6 +135,8 @@
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // info about the receiver that is currently running.
+ boolean mIsReceiverAppRunning; // Was the receiver's app already running.
+
// Private refcount-management bookkeeping; start > 0
static AtomicInteger sNextToken = new AtomicInteger(1);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 45265ac..363c9d0 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1392,25 +1392,17 @@
void cancelAllCompactions(CancelCompactReason reason) {
synchronized (mProcLock) {
- int size = mPendingCompactionProcesses.size();
- ProcessRecord record;
- for (int i=0; i < size; ++i) {
- record = mPendingCompactionProcesses.get(i);
- cancelCompactionForProcess(record, reason);
- // The process record is kept alive after compactions are cleared,
- // so make sure to reset the compaction state to avoid skipping any future
- // compactions due to a stale value here.
- record.mOptRecord.setHasPendingCompact(false);
+ while(!mPendingCompactionProcesses.isEmpty()) {
+ cancelCompactionForProcess(mPendingCompactionProcesses.get(0), reason);
}
mPendingCompactionProcesses.clear();
}
- cancelCompaction();
}
@GuardedBy("mProcLock")
void cancelCompactionForProcess(ProcessRecord app, CancelCompactReason cancelReason) {
boolean cancelled = false;
- if (!mPendingCompactionProcesses.isEmpty() && mPendingCompactionProcesses.contains(app)) {
+ if (mPendingCompactionProcesses.contains(app)) {
app.mOptRecord.setHasPendingCompact(false);
mPendingCompactionProcesses.remove(app);
cancelled = true;
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 4ff1a12..9abd01a 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -75,6 +75,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -935,7 +936,9 @@
return;
}
- mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
+ TimeoutRecord timeoutRecord = TimeoutRecord.forContentProvider(
+ "ContentProvider not responding");
+ mService.mAnrHelper.appNotResponding(host, timeoutRecord);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index f88a8ce..efc2a27 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -16,10 +16,27 @@
package com.android.server.am;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
-import android.os.ProcessStartTime;
/**
* This class describes various information required to start a process.
@@ -32,6 +49,9 @@
*
* The {@code mHostingZygote} field describes from which Zygote the new process should be spawned.
*
+ * The {@code mTriggerType} field describes the trigger that started this processs. This could be
+ * an alarm or a push-message for a broadcast, for example. This is purely for logging and stats.
+ *
* {@code mDefiningPackageName} contains the packageName of the package that defines the
* component we want to start; this can be different from the packageName and uid in the
* ApplicationInfo that we're creating the process with, in case the service is a
@@ -71,7 +91,10 @@
public static final String HOSTING_TYPE_TOP_ACTIVITY = "top-activity";
public static final String HOSTING_TYPE_EMPTY = "";
- private @NonNull final String mHostingType;
+ public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
+ public static final String TRIGGER_TYPE_ALARM = "alarm";
+
+ @NonNull private final String mHostingType;
private final String mHostingName;
private final int mHostingZygote;
private final String mDefiningPackageName;
@@ -79,11 +102,12 @@
private final boolean mIsTopApp;
private final String mDefiningProcessName;
@Nullable private final String mAction;
+ @NonNull private final String mTriggerType;
public HostingRecord(@NonNull String hostingType) {
this(hostingType, null /* hostingName */, REGULAR_ZYGOTE, null /* definingPackageName */,
-1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
- null /* action */);
+ null /* action */, TRIGGER_TYPE_UNKNOWN);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName) {
@@ -91,22 +115,23 @@
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
- @Nullable String action) {
+ @Nullable String action, @Nullable String triggerType) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
null /* definingPackageName */, -1 /* mDefiningUid */, false /* isTopApp */,
- null /* definingProcessName */, action);
+ null /* definingProcessName */, action, triggerType);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName,
String definingPackageName, int definingUid, String definingProcessName) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE, definingPackageName,
- definingUid, false /* isTopApp */, definingProcessName, null /* action */);
+ definingUid, false /* isTopApp */, definingProcessName, null /* action */,
+ TRIGGER_TYPE_UNKNOWN);
}
public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
- null /* definingProcessName */, null /* action */);
+ null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
public HostingRecord(@NonNull String hostingType, String hostingName) {
@@ -121,12 +146,12 @@
private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote) {
this(hostingType, hostingName, hostingZygote, null /* definingPackageName */,
-1 /* mDefiningUid */, false /* isTopApp */, null /* definingProcessName */,
- null /* action */);
+ null /* action */, TRIGGER_TYPE_UNKNOWN);
}
private HostingRecord(@NonNull String hostingType, String hostingName, int hostingZygote,
String definingPackageName, int definingUid, boolean isTopApp,
- String definingProcessName, @Nullable String action) {
+ String definingProcessName, @Nullable String action, String triggerType) {
mHostingType = hostingType;
mHostingName = hostingName;
mHostingZygote = hostingZygote;
@@ -135,6 +160,7 @@
mIsTopApp = isTopApp;
mDefiningProcessName = definingProcessName;
mAction = action;
+ mTriggerType = triggerType;
}
public @NonNull String getType() {
@@ -188,6 +214,11 @@
return mAction;
}
+ /** Returns the type of trigger that led to this process start. */
+ public @NonNull String getTriggerType() {
+ return mTriggerType;
+ }
+
/**
* Creates a HostingRecord for a process that must spawn from the webview zygote
* @param hostingName name of the component to be hosted in this process
@@ -197,7 +228,7 @@
String definingPackageName, int definingUid, String definingProcessName) {
return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
WEBVIEW_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
- definingProcessName, null /* action */);
+ definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
/**
@@ -211,7 +242,7 @@
int definingUid, String definingProcessName) {
return new HostingRecord(HostingRecord.HOSTING_TYPE_EMPTY, hostingName.toShortString(),
APP_ZYGOTE, definingPackageName, definingUid, false /* isTopApp */,
- definingProcessName, null /* action */);
+ definingProcessName, null /* action */, TRIGGER_TYPE_UNKNOWN);
}
/**
@@ -236,35 +267,49 @@
public static int getHostingTypeIdStatsd(@NonNull String hostingType) {
switch(hostingType) {
case HOSTING_TYPE_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ACTIVITY;
case HOSTING_TYPE_ADDED_APPLICATION:
- return ProcessStartTime.HOSTING_TYPE_ADDED_APPLICATION;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ADDED_APPLICATION;
case HOSTING_TYPE_BACKUP:
- return ProcessStartTime.HOSTING_TYPE_BACKUP;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BACKUP;
case HOSTING_TYPE_BROADCAST:
- return ProcessStartTime.HOSTING_TYPE_BROADCAST;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_BROADCAST;
case HOSTING_TYPE_CONTENT_PROVIDER:
- return ProcessStartTime.HOSTING_TYPE_CONTENT_PROVIDER;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_CONTENT_PROVIDER;
case HOSTING_TYPE_LINK_FAIL:
- return ProcessStartTime.HOSTING_TYPE_LINK_FAIL;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_LINK_FAIL;
case HOSTING_TYPE_ON_HOLD:
- return ProcessStartTime.HOSTING_TYPE_ON_HOLD;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_ON_HOLD;
case HOSTING_TYPE_NEXT_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_NEXT_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_ACTIVITY;
case HOSTING_TYPE_NEXT_TOP_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_NEXT_TOP_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_NEXT_TOP_ACTIVITY;
case HOSTING_TYPE_RESTART:
- return ProcessStartTime.HOSTING_TYPE_RESTART;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_RESTART;
case HOSTING_TYPE_SERVICE:
- return ProcessStartTime.HOSTING_TYPE_SERVICE;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
case HOSTING_TYPE_SYSTEM:
- return ProcessStartTime.HOSTING_TYPE_SYSTEM;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
case HOSTING_TYPE_TOP_ACTIVITY:
- return ProcessStartTime.HOSTING_TYPE_TOP_ACTIVITY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
case HOSTING_TYPE_EMPTY:
- return ProcessStartTime.HOSTING_TYPE_EMPTY;
+ return PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_EMPTY;
default:
- return ProcessStartTime.HOSTING_TYPE_UNKNOWN;
+ return PROCESS_START_TIME__TYPE__UNKNOWN;
+ }
+ }
+
+ /**
+ * Map the string triggerType to enum TriggerType defined in ProcessStartTime proto.
+ * @param triggerType
+ * @return enum TriggerType defined in ProcessStartTime proto
+ */
+ public static int getTriggerTypeForStatsd(@NonNull String triggerType) {
+ switch(triggerType) {
+ case TRIGGER_TYPE_ALARM:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+ default:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index b27665a..3a8a077 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
@@ -254,7 +255,9 @@
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
- boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
+ boolean aboveSystem, TimeoutRecord timeoutRecord,
+ boolean onlyDumpSelf) {
+ String annotation = timeoutRecord.mReason;
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseArray<Boolean> lastPids = new SparseArray<>(20);
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 487d19a..6e289b1 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -220,16 +220,40 @@
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
+ boolean batteryModeSupported = false;
+ boolean perfModeSupported = false;
+ int [] modes = gameManagerService.getAvailableGameModes(packageName);
+
+ for (int mode : modes) {
+ if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+ perfModeSupported = true;
+ } else if (mode == GameManager.GAME_MODE_BATTERY) {
+ batteryModeSupported = true;
+ }
+ }
+
switch (gameMode.toLowerCase(Locale.getDefault())) {
case "2":
case "performance":
- gameManagerService.setGameModeConfigOverride(packageName, userId,
- GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio);
+ if (perfModeSupported) {
+ gameManagerService.setGameModeConfigOverride(packageName, userId,
+ GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio);
+ } else {
+ pw.println("Game mode: " + gameMode + " not supported by "
+ + packageName);
+ return -1;
+ }
break;
case "3":
case "battery":
- gameManagerService.setGameModeConfigOverride(packageName, userId,
- GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio);
+ if (batteryModeSupported) {
+ gameManagerService.setGameModeConfigOverride(packageName, userId,
+ GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio);
+ } else {
+ pw.println("Game mode: " + gameMode + " not supported by "
+ + packageName);
+ return -1;
+ }
break;
default:
pw.println("Invalid game mode: " + gameMode);
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index d16fe12..d4ef638 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -343,6 +343,9 @@
*
* Calling this multiple times for duplicate requests will be no-ops, returning true.
*
+ * TODO(b/239130847): Maintain the proximity state in AttentionManagerService and change this
+ * to a polling API.
+ *
* @return {@code true} if the framework was able to dispatch the request
*/
@VisibleForTesting
@@ -853,9 +856,6 @@
@GuardedBy("mLock")
private void cancelAndUnbindLocked() {
synchronized (mLock) {
- if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) {
- return;
- }
if (mCurrentAttentionCheck != null) {
cancel();
}
@@ -937,7 +937,7 @@
}
}
- class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal {
+ class TestableProximityUpdateCallbackInternal implements ProximityUpdateCallbackInternal {
private double mLastCallbackCode = PROXIMITY_UNKNOWN;
@Override
@@ -1069,6 +1069,7 @@
private void resetStates() {
synchronized (mLock) {
mCurrentProximityUpdate = null;
+ cancelAndUnbindLocked();
}
mComponentName = resolveAttentionService(mContext);
}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 26a6312..5620dc3 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -337,7 +337,7 @@
boolean hasPublicClients = false;
while (clientIterator.hasNext()) {
RecMonitorClient rmc = clientIterator.next();
- if (rcdb.equals(rmc.mDispatcherCb)) {
+ if (rcdb.asBinder().equals(rmc.mDispatcherCb.asBinder())) {
rmc.release();
clientIterator.remove();
} else {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index e53aef7..99e709e 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -127,12 +127,13 @@
/**
* Returns the default size of the surface associated with the display, or null if the surface
- * is not provided for layer mirroring by SurfaceFlinger.
- * Only used for mirroring started from MediaProjection.
+ * is not provided for layer mirroring by SurfaceFlinger. For non virtual displays, this will
+ * be the actual display device's size.
*/
@Nullable
public Point getDisplaySurfaceDefaultSizeLocked() {
- return null;
+ DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
+ return new Point(displayDeviceInfo.width, displayDeviceInfo.height);
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index b5aa7b1..4f3fd64 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,6 +33,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
@@ -65,13 +66,13 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import javax.xml.datatype.DatatypeConfigurationException;
/**
- * Reads and stores display-specific configurations.
- * File format:
- * <pre>
+ * Reads and stores display-specific configurations. File format:
+ * <pre>
* {@code
* <displayConfiguration>
* <densityMapping>
@@ -147,6 +148,15 @@
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
*
+ * <autoBrightness>
+ * <brighteningLightDebounceMillis>
+ * 2000
+ * </brighteningLightDebounceMillis>
+ * <darkeningLightDebounceMillis>
+ * 1000
+ * </darkeningLightDebounceMillis>
+ * </autoBrightness>
+ *
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
* <screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>
* <screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>
@@ -224,6 +234,9 @@
// Length of the ambient light horizon used to calculate short-term estimate of ambient light.
private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000;
+ // Invalid value of AutoBrightness brightening and darkening light debounce
+ private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
+
@VisibleForTesting
static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
@@ -281,6 +294,14 @@
private String mLoadedFrom = null;
private Spline mSdrToHdrRatioSpline;
+ // Represents the auto-brightness brightening light debounce.
+ private long mAutoBrightnessBrighteningLightDebounce =
+ INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+
+ // Represents the auto-brightness darkening light debounce.
+ private long mAutoBrightnessDarkeningLightDebounce =
+ INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+
// Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
// data, which comes from the ddc, and the current one, which may be the DeviceConfig
// overwritten value.
@@ -293,8 +314,8 @@
}
/**
- * Creates an instance for the specified display.
- * Tries to find a file with identifier in the following priority order:
+ * Creates an instance for the specified display. Tries to find a file with identifier in the
+ * following priority order:
* <ol>
* <li>physicalDisplayId</li>
* <li>physicalDisplayId without a stable flag (old system)</li>
@@ -314,11 +335,12 @@
}
/**
- * Creates an instance using global values since no display device config xml exists.
- * Uses values from config or PowerManager.
+ * Creates an instance using global values since no display device config xml exists. Uses
+ * values from config or PowerManager.
*
- * @param context
- * @param useConfigXml
+ * @param context The context from which the DisplayDeviceConfig is to be constructed.
+ * @param useConfigXml A flag indicating if values are to be loaded from the configuration file,
+ * or the default values.
* @return A configuration instance.
*/
public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
@@ -450,8 +472,8 @@
}
/**
- * Calculates the backlight value, as recognised by the HAL, from the brightness value
- * given that the rest of the system deals with.
+ * Calculates the backlight value, as recognised by the HAL, from the brightness value given
+ * that the rest of the system deals with.
*
* @param brightness value on the framework scale of 0-1
* @return backlight value on the HAL scale of 0-1
@@ -502,13 +524,13 @@
if (DEBUG) {
Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness
- + " backlight " + backlight
- + " nits " + nits
- + " ratio " + ratio
- + " hdrNits " + hdrNits
- + " hdrBacklight " + hdrBacklight
- + " hdrBrightness " + hdrBrightness
- );
+ + " backlight " + backlight
+ + " nits " + nits
+ + " ratio " + ratio
+ + " hdrNits " + hdrNits
+ + " hdrBacklight " + hdrBacklight
+ + " hdrBrightness " + hdrBrightness
+ );
}
return hdrBrightness;
}
@@ -590,8 +612,8 @@
/**
* @param quirkValue The quirk to test.
- * @return {@code true} if the specified quirk is present in this configuration,
- * {@code false} otherwise.
+ * @return {@code true} if the specified quirk is present in this configuration, {@code false}
+ * otherwise.
*/
public boolean hasQuirk(String quirkValue) {
return mQuirks != null && mQuirks.contains(quirkValue);
@@ -625,6 +647,20 @@
return BrightnessThrottlingData.create(mBrightnessThrottlingData);
}
+ /**
+ * @return Auto brightness darkening light debounce
+ */
+ public long getAutoBrightnessDarkeningLightDebounce() {
+ return mAutoBrightnessDarkeningLightDebounce;
+ }
+
+ /**
+ * @return Auto brightness brightening light debounce
+ */
+ public long getAutoBrightnessBrighteningLightDebounce() {
+ return mAutoBrightnessBrighteningLightDebounce;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -663,14 +699,18 @@
+ ", mProximitySensor=" + mProximitySensor
+ ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
+ ", mDensityMapping= " + mDensityMapping
+ + ", mAutoBrightnessBrighteningLightDebounce= "
+ + mAutoBrightnessBrighteningLightDebounce
+ + ", mAutoBrightnessDarkeningLightDebounce= "
+ + mAutoBrightnessDarkeningLightDebounce
+ "}";
}
private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
String suffixFormat, long idNumber) {
- final String suffix = String.format(suffixFormat, idNumber);
- final String filename = String.format(CONFIG_FILE_FORMAT, suffix);
+ final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
+ final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
final File filePath = Environment.buildPath(
baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
final DisplayDeviceConfig config = new DisplayDeviceConfig(context);
@@ -719,6 +759,7 @@
loadProxSensorFromDdc(config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
+ loadAutoBrightnessConfigValues(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -899,8 +940,8 @@
if (i > 0) {
if (nits[i] < nits[i - 1]) {
Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest "
- + " of configuration. nits: " + nits[i] + " < "
- + nits[i - 1]);
+ + " of configuration. nits: " + nits[i] + " < "
+ + nits[i - 1]);
return null;
}
}
@@ -927,7 +968,7 @@
final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();
// At least 1 point is guaranteed by the display device config schema
List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels =
- new ArrayList<>(points.size());
+ new ArrayList<>(points.size());
boolean badConfig = false;
for (BrightnessThrottlingPoint point : points) {
@@ -938,7 +979,7 @@
}
throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel(
- convertThermalStatus(status), point.getBrightness().floatValue()));
+ convertThermalStatus(status), point.getBrightness().floatValue()));
}
if (!badConfig) {
@@ -947,6 +988,41 @@
}
}
+ private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
+ loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
+ loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
+ }
+
+ /**
+ * Loads the auto-brightness brightening light debounce. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessBrighteningLightDebounce(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getBrighteningLightDebounceMillis() == null) {
+ mAutoBrightnessBrighteningLightDebounce = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
+ } else {
+ mAutoBrightnessBrighteningLightDebounce =
+ autoBrightnessConfig.getBrighteningLightDebounceMillis().intValue();
+ }
+ }
+
+ /**
+ * Loads the auto-brightness darkening light debounce. Internally, this takes care of loading
+ * the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessDarkeningLightDebounce(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getDarkeningLightDebounceMillis() == null) {
+ mAutoBrightnessDarkeningLightDebounce = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce);
+ } else {
+ mAutoBrightnessDarkeningLightDebounce =
+ autoBrightnessConfig.getDarkeningLightDebounceMillis().intValue();
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1058,17 +1134,17 @@
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]);
}
mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBrightness, mBacklight)
- : Spline.createSpline(mBrightness, mBacklight);
+ ? Spline.createLinearSpline(mBrightness, mBacklight)
+ : Spline.createSpline(mBrightness, mBacklight);
mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBacklight, mBrightness)
- : Spline.createSpline(mBacklight, mBrightness);
+ ? Spline.createLinearSpline(mBacklight, mBrightness)
+ : Spline.createSpline(mBacklight, mBrightness);
mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mBacklight, mNits)
- : Spline.createSpline(mBacklight, mNits);
+ ? Spline.createLinearSpline(mBacklight, mNits)
+ : Spline.createSpline(mBacklight, mNits);
mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR
- ? Spline.createLinearSpline(mNits, mBacklight)
- : Spline.createSpline(mNits, mBacklight);
+ ? Spline.createLinearSpline(mNits, mBacklight)
+ : Spline.createSpline(mNits, mBacklight);
}
private void loadQuirks(DisplayConfiguration config) {
@@ -1111,7 +1187,7 @@
if (mHbmData.minimumHdrPercentOfScreen > 1
|| mHbmData.minimumHdrPercentOfScreen < 0) {
Slog.w(TAG, "Invalid minimum HDR percent of screen: "
- + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
+ + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
}
} else {
@@ -1235,7 +1311,7 @@
ambientBrightnessThresholds.getDarkeningThresholds();
final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
- final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
+ final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();
if (ambientBrighteningThreshold != null) {
mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
@@ -1330,8 +1406,8 @@
}
/**
- * @return True if the sensor matches both the specified name and type, or one if only
- * one is specified (not-empty). Always returns false if both parameters are null or empty.
+ * @return True if the sensor matches both the specified name and type, or one if only one
+ * is specified (not-empty). Always returns false if both parameters are null or empty.
*/
public boolean matches(String sensorName, String sensorType) {
final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
@@ -1446,6 +1522,7 @@
return otherThrottlingLevel.thermalStatus == this.thermalStatus
&& otherThrottlingLevel.brightness == this.brightness;
}
+
@Override
public int hashCode() {
int result = 1;
@@ -1455,8 +1532,11 @@
}
}
- static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
- {
+
+ /**
+ * Creates multiple teperature based throttling levels of brightness
+ */
+ public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) {
if (throttlingLevels == null || throttlingLevels.size() == 0) {
Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");
return null;
@@ -1498,8 +1578,9 @@
}
static public BrightnessThrottlingData create(BrightnessThrottlingData other) {
- if (other == null)
+ if (other == null) {
return null;
+ }
return BrightnessThrottlingData.create(other.throttlingLevels);
}
@@ -1508,8 +1589,8 @@
@Override
public String toString() {
return "BrightnessThrottlingData{"
- + "throttlingLevels:" + throttlingLevels
- + "} ";
+ + "throttlingLevels:" + throttlingLevels
+ + "} ";
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2dd3864..e53ac37 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -70,8 +70,6 @@
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
-import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -2214,24 +2212,10 @@
private void configureDisplayLocked(SurfaceControl.Transaction t, DisplayDevice device) {
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
// Find the logical display that the display device is showing.
// Certain displays only ever show their own content.
LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
- // Proceed with display-managed mirroring only if window manager will not be handling it.
- if (!ownContent && !device.isWindowManagerMirroringLocked()) {
- // Only mirror the display if content recording is not taking place in WM.
- if (display != null && !display.hasContentLocked()) {
- // If the display does not have any content of its own, then
- // automatically mirror the requested logical display contents if possible.
- display = mLogicalDisplayMapper.getDisplayLocked(
- device.getDisplayIdToMirrorLocked());
- }
- if (display == null) {
- display = mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY);
- }
- }
// Apply the logical display configuration to the display device.
if (display == null) {
@@ -2546,18 +2530,6 @@
}
@VisibleForTesting
- int getDisplayIdToMirrorInternal(int displayId) {
- synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
- if (display != null) {
- final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
- return displayDevice.getDisplayIdToMirrorLocked();
- }
- return Display.INVALID_DISPLAY;
- }
- }
-
- @VisibleForTesting
Surface getVirtualDisplaySurfaceInternal(IBinder appToken) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
@@ -3853,6 +3825,37 @@
return null;
}
}
+
+ @Override
+ public int getDisplayIdToMirror(int displayId) {
+ synchronized (mSyncRoot) {
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (display == null) {
+ return Display.INVALID_DISPLAY;
+ }
+
+ final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+ final boolean ownContent = (displayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
+ // If the display has enabled mirroring, but specified that it will be managed by
+ // WindowManager, return an invalid display id. This is to ensure we don't
+ // accidentally select the display id to mirror based on DM logic and instead allow
+ // the caller to specify what area to mirror.
+ if (ownContent || displayDevice.isWindowManagerMirroringLocked()) {
+ return Display.INVALID_DISPLAY;
+ }
+
+ int displayIdToMirror = displayDevice.getDisplayIdToMirrorLocked();
+ LogicalDisplay displayToMirror = mLogicalDisplayMapper.getDisplayLocked(
+ displayIdToMirror);
+ // If the displayId for the requested mirror doesn't exist, fallback to mirroring
+ // default display.
+ if (displayToMirror == null) {
+ displayIdToMirror = Display.DEFAULT_DISPLAY;
+ }
+ return displayIdToMirror;
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6150b49..6f3a0c5 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -162,7 +162,7 @@
private static final int RINGBUFFER_MAX = 100;
- private final String TAG;
+ private final String mTag;
private final Object mLock = new Object();
@@ -510,7 +510,7 @@
mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
- TAG = "DisplayPowerController[" + mDisplayId + "]";
+ mTag = "DisplayPowerController[" + mDisplayId + "]";
mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
@@ -600,7 +600,7 @@
displayWhiteBalanceSettings.setCallbacks(this);
displayWhiteBalanceController.setCallbacks(this);
} catch (Exception e) {
- Slog.e(TAG, "failed to set up display white-balance: " + e);
+ Slog.e(mTag, "failed to set up display white-balance: " + e);
}
}
mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
@@ -667,7 +667,7 @@
&& mInteractiveModeBrightnessMapper == null)
|| (mAutomaticBrightnessController.isInIdleMode()
&& mIdleModeBrightnessMapper == null)) {
- Log.w(TAG, "No brightness mapping available to recalculate splines for this mode");
+ Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
return;
}
@@ -744,7 +744,7 @@
public boolean requestPowerState(DisplayPowerRequest request,
boolean waitForNegativeProximity) {
if (DEBUG) {
- Slog.d(TAG, "requestPowerState: "
+ Slog.d(mTag, "requestPowerState: "
+ request + ", waitForNegativeProximity=" + waitForNegativeProximity);
}
@@ -797,7 +797,7 @@
public void onDisplayChanged() {
final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
if (device == null) {
- Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
+ Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
+ mLogicalDisplay.getDisplayIdLocked());
return;
}
@@ -995,10 +995,10 @@
screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
screenDarkeningMinThreshold, screenBrighteningMinThreshold);
- long brighteningLightDebounce = resources.getInteger(
- com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
- long darkeningLightDebounce = resources.getInteger(
- com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce);
+ long brighteningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounce();
+ long darkeningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounce();
boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
@@ -1011,7 +1011,7 @@
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
} else if (initialLightSensorRate > lightSensorRate) {
- Slog.w(TAG, "Expected config_autoBrightnessInitialLightSensorRate ("
+ Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+ initialLightSensorRate + ") to be less than or equal to "
+ "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
}
@@ -1057,7 +1057,7 @@
if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
mNitsRange = mDisplayDeviceConfig.getNits();
} else {
- Slog.w(TAG, "Screen brightness nits configuration is unavailable; falling back");
+ Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits));
}
@@ -1217,6 +1217,7 @@
}
assert (state != Display.STATE_UNKNOWN);
+ boolean skipRampBecauseOfProximityChangeToNegative = false;
// Apply the proximity sensor.
if (mProximitySensor != null) {
if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
@@ -1254,6 +1255,7 @@
// the screen back on. Also turn it back on if we've been asked to ignore the
// prox sensor temporarily.
mScreenOffBecauseOfProximity = false;
+ skipRampBecauseOfProximityChangeToNegative = true;
sendOnProximityNegativeWithWakelock();
}
} else {
@@ -1326,9 +1328,6 @@
}
final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
- if (autoBrightnessAdjustmentChanged) {
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- }
// Use the autobrightness adjustment override if set.
final float autoBrightnessAdjustment;
@@ -1536,8 +1535,8 @@
final boolean wasOrWillBeInVr =
(state == Display.STATE_VR || oldState == Display.STATE_VR);
- final boolean initialRampSkip =
- state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE;
+ final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
+ != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
// While dozing, sometimes the brightness is split into buckets. Rather than animating
// through the buckets, which is unlikely to be smooth in the first place, just jump
// right to the suggested brightness.
@@ -1626,13 +1625,13 @@
// Log any changes to what is currently driving the brightness setting.
if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
- Slog.v(TAG, "Brightness [" + brightnessState + "] reason changing to: '"
+ Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
+ mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
+ "', previous reason: '" + mBrightnessReason + "'.");
mBrightnessReason.set(mBrightnessReasonTemp);
} else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
&& userSetBrightnessChanged) {
- Slog.v(TAG, "Brightness [" + brightnessState + "] manual adjustment.");
+ Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
}
@@ -1650,7 +1649,7 @@
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
- && mLastBrightnessEvent.getReason().getReason()
+ && mLastBrightnessEvent.getReason().getReason()
== BrightnessReason.REASON_TEMPORARY;
if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
|| brightnessAdjustmentFlags != 0) {
@@ -1662,7 +1661,7 @@
newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
? BrightnessEvent.FLAG_USER_SET : 0));
- Slog.i(TAG, newEvent.toString(/* includeTime= */ false));
+ Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
@@ -1683,9 +1682,9 @@
// Note that we do not wait for the brightness ramp animation to complete before
// reporting the display is ready because we only need to ensure the screen is in the
// right power state even as it continues to converge on the desired brightness.
- final boolean ready = mPendingScreenOnUnblocker == null &&
- (!mColorFadeEnabled ||
- (!mColorFadeOnAnimator.isStarted() && !mColorFadeOffAnimator.isStarted()))
+ final boolean ready = mPendingScreenOnUnblocker == null
+ && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
+ && !mColorFadeOffAnimator.isStarted()))
&& mPowerState.waitUntilClean(mCleanListener);
final boolean finished = ready
&& !mScreenBrightnessRampAnimator.isAnimating();
@@ -1700,7 +1699,7 @@
// Grab a wake lock if we have unfinished business.
if (!finished && !mUnfinishedBusiness) {
if (DEBUG) {
- Slog.d(TAG, "Unfinished business...");
+ Slog.d(mTag, "Unfinished business...");
}
mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
mUnfinishedBusiness = true;
@@ -1714,7 +1713,7 @@
mDisplayReadyLocked = true;
if (DEBUG) {
- Slog.d(TAG, "Display ready!");
+ Slog.d(mTag, "Display ready!");
}
}
}
@@ -1724,7 +1723,7 @@
// Release the wake lock when we have no unfinished business.
if (finished && mUnfinishedBusiness) {
if (DEBUG) {
- Slog.d(TAG, "Finished business...");
+ Slog.d(mTag, "Finished business...");
}
mUnfinishedBusiness = false;
mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
@@ -1868,7 +1867,7 @@
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
mPendingScreenOnUnblocker = new ScreenOnUnblocker();
mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
- Slog.i(TAG, "Blocking screen on until initial contents have been drawn.");
+ Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
}
}
@@ -1876,7 +1875,7 @@
if (mPendingScreenOnUnblocker != null) {
mPendingScreenOnUnblocker = null;
long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
- Slog.i(TAG, "Unblocked screen on after " + delay + " ms");
+ Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
}
}
@@ -1886,7 +1885,7 @@
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
mPendingScreenOffUnblocker = new ScreenOffUnblocker();
mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
- Slog.i(TAG, "Blocking screen off");
+ Slog.i(mTag, "Blocking screen off");
}
}
@@ -1894,7 +1893,7 @@
if (mPendingScreenOffUnblocker != null) {
mPendingScreenOffUnblocker = null;
long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
- Slog.i(TAG, "Unblocked screen off after " + delay + " ms");
+ Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
}
}
@@ -2020,7 +2019,7 @@
private void animateScreenBrightness(float target, float sdrTarget, float rate) {
if (DEBUG) {
- Slog.d(TAG, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+ Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+ ", rate=" + rate);
}
if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
@@ -2033,8 +2032,8 @@
private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
// If there is already an animation in progress, don't interfere with it.
- if (mColorFadeEnabled &&
- (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
+ if (mColorFadeEnabled
+ && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
if (target != Display.STATE_ON) {
return;
}
@@ -2081,9 +2080,8 @@
if (mPowerState.getColorFadeLevel() == 1.0f) {
mPowerState.dismissColorFade();
} else if (mPowerState.prepareColorFade(mContext,
- mColorFadeFadesConfig ?
- ColorFade.MODE_FADE :
- ColorFade.MODE_WARM_UP)) {
+ mColorFadeFadesConfig
+ ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
mColorFadeOnAnimator.start();
} else {
mColorFadeOnAnimator.end();
@@ -2262,7 +2260,7 @@
if (mProximity != mPendingProximity) {
// if the status of the sensor changed, stop ignoring.
mIgnoreProximityUntilChanged = false;
- Slog.i(TAG, "No longer ignoring proximity [" + mPendingProximity + "]");
+ Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
}
// Sensor reading accepted. Apply the change then release the wake lock.
mProximity = mPendingProximity;
@@ -2308,14 +2306,15 @@
private void handleSettingsChange(boolean userSwitch) {
mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+ mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
if (userSwitch) {
// Don't treat user switches as user initiated change.
setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+ updateAutoBrightnessAdjustment();
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
}
- mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
// We don't bother with a pending variable for VR screen brightness since we just
// immediately adapt to it.
mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
@@ -2384,6 +2383,7 @@
}
mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
return true;
}
@@ -2447,7 +2447,7 @@
&& mProximity == PROXIMITY_POSITIVE) {
// Only ignore if it is still reporting positive (near)
mIgnoreProximityUntilChanged = true;
- Slog.i(TAG, "Ignoring proximity");
+ Slog.i(mTag, "Ignoring proximity");
updatePowerState();
}
}
@@ -2517,25 +2517,25 @@
pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
- pw.println(" mAllowAutoBrightnessWhileDozingConfig=" +
- mAllowAutoBrightnessWhileDozingConfig);
+ pw.println(" mAllowAutoBrightnessWhileDozingConfig="
+ + mAllowAutoBrightnessWhileDozingConfig);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
synchronized (mCachedBrightnessInfo) {
- pw.println(" mCachedBrightnessInfo.brightness=" +
- mCachedBrightnessInfo.brightness.value);
- pw.println(" mCachedBrightnessInfo.adjustedBrightness=" +
- mCachedBrightnessInfo.adjustedBrightness.value);
- pw.println(" mCachedBrightnessInfo.brightnessMin=" +
- mCachedBrightnessInfo.brightnessMin.value);
- pw.println(" mCachedBrightnessInfo.brightnessMax=" +
- mCachedBrightnessInfo.brightnessMax.value);
+ pw.println(" mCachedBrightnessInfo.brightness="
+ + mCachedBrightnessInfo.brightness.value);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness="
+ + mCachedBrightnessInfo.adjustedBrightness.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMin="
+ + mCachedBrightnessInfo.brightnessMin.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMax="
+ + mCachedBrightnessInfo.brightnessMax.value);
pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
- pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
- mCachedBrightnessInfo.hbmTransitionPoint.value);
- pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
- mCachedBrightnessInfo.brightnessMaxReason.value);
+ pw.println(" mCachedBrightnessInfo.hbmTransitionPoint="
+ + mCachedBrightnessInfo.hbmTransitionPoint.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMaxReason ="
+ + mCachedBrightnessInfo.brightnessMaxReason.value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2753,7 +2753,7 @@
}
private final class DisplayControllerHandler extends Handler {
- public DisplayControllerHandler(Looper looper) {
+ DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@@ -2848,7 +2848,7 @@
private final class SettingsObserver extends ContentObserver {
- public SettingsObserver(Handler handler) {
+ SettingsObserver(Handler handler) {
super(handler);
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 3cb3431..ffdb66d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -53,6 +53,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.ClientBrokerProto;
import java.util.Collections;
@@ -162,7 +163,7 @@
* The remote callback interface for this client. This will be set to null whenever the
* client connection is closed (either explicitly or via binder death).
*/
- private IContextHubClientCallback mCallbackInterface;
+ private IContextHubClientCallback mContextHubClientCallback;
/*
* True if the client is still registered with the Context Hub Service, false otherwise.
@@ -184,12 +185,13 @@
* unregistered.
*/
@GuardedBy("mWakeLock")
- private boolean mIsWakeLockActive = true;
+ private boolean mIsWakelockUsable = true;
/*
* Internal interface used to invoke client callbacks.
*/
- private interface CallbackConsumer {
+ @VisibleForTesting
+ interface CallbackConsumer {
void accept(IContextHubClientCallback callback) throws RemoteException;
}
@@ -325,17 +327,24 @@
}
}
- private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
- ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
- short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
- ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
- long nanoAppId, String packageName) {
+ private ContextHubClientBroker(
+ Context context,
+ IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager,
+ ContextHubInfo contextHubInfo,
+ short hostEndPointId,
+ IContextHubClientCallback callback,
+ String attributionTag,
+ ContextHubTransactionManager transactionManager,
+ PendingIntent pendingIntent,
+ long nanoAppId,
+ String packageName) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubInfo = contextHubInfo;
mHostEndPointId = hostEndPointId;
- mCallbackInterface = callback;
+ mContextHubClientCallback = callback;
if (pendingIntent == null) {
mPendingIntentRequest = new PendingIntentRequest();
} else {
@@ -369,7 +378,7 @@
sendHostEndpointConnectedEvent();
}
- /* package */ ContextHubClientBroker(
+ ContextHubClientBroker(
Context context,
IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager,
@@ -393,7 +402,7 @@
packageName);
}
- /* package */ ContextHubClientBroker(
+ ContextHubClientBroker(
Context context,
IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager,
@@ -504,36 +513,50 @@
}
}
- /* package */ String getPackageName() {
+ String getPackageName() {
return mPackage;
}
+ @VisibleForTesting
+ boolean isWakelockUsable() {
+ synchronized (mWakeLock) {
+ return mIsWakelockUsable;
+ }
+ }
+
+ @VisibleForTesting
+ WakeLock getWakeLock() {
+ synchronized (mWakeLock) {
+ return mWakeLock;
+ }
+ }
+
/**
* Used to override the attribution tag with a newer value if a PendingIntent broker is
* retrieved.
*/
- /* package */ void setAttributionTag(String attributionTag) {
+ void setAttributionTag(String attributionTag) {
mAttributionTag = attributionTag;
}
/**
* @return the attribution tag associated with this broker.
*/
- /* package */ String getAttributionTag() {
+ String getAttributionTag() {
return mAttributionTag;
}
/**
* @return the ID of the context hub this client is attached to
*/
- /* package */ int getAttachedContextHubId() {
+ int getAttachedContextHubId() {
return mAttachedContextHubInfo.getId();
}
/**
* @return the host endpoint ID of this client
*/
- /* package */ short getHostEndPointId() {
+ short getHostEndPointId() {
return mHostEndPointId;
}
@@ -542,17 +565,19 @@
*
* @param message the message that came from a nanoapp
* @param nanoappPermissions permissions required to communicate with the nanoapp sending this
- * message
+ * message
* @param messagePermissions permissions required to consume the message being delivered. These
- * permissions are what will be attributed to the client through noteOp.
+ * permissions are what will be attributed to the client through noteOp.
*/
- /* package */ void sendMessageToClient(
- NanoAppMessage message, List<String> nanoappPermissions,
+ void sendMessageToClient(
+ NanoAppMessage message,
+ List<String> nanoappPermissions,
List<String> messagePermissions) {
long nanoAppId = message.getNanoAppId();
- int authState = updateNanoAppAuthState(nanoAppId, nanoappPermissions,
- false /* gracePeriodExpired */);
+ int authState =
+ updateNanoAppAuthState(
+ nanoAppId, nanoappPermissions, false /* gracePeriodExpired */);
// If in the grace period, the host may not receive any messages containing permissions
// covered data.
@@ -584,7 +609,7 @@
*
* @param nanoAppId the ID of the nanoapp that was loaded.
*/
- /* package */ void onNanoAppLoaded(long nanoAppId) {
+ void onNanoAppLoaded(long nanoAppId) {
// Check the latest state to see if the loaded nanoapp's permissions changed such that the
// host app can communicate with it again.
checkNanoappPermsAsync();
@@ -599,16 +624,14 @@
*
* @param nanoAppId the ID of the nanoapp that was unloaded.
*/
- /* package */ void onNanoAppUnloaded(long nanoAppId) {
+ void onNanoAppUnloaded(long nanoAppId) {
invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId));
sendPendingIntent(
() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId), nanoAppId);
}
- /**
- * Notifies the client of a hub reset event if the connection is open.
- */
- /* package */ void onHubReset() {
+ /** Notifies the client of a hub reset event if the connection is open. */
+ void onHubReset() {
invokeCallback(IContextHubClientCallback::onHubReset);
sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
@@ -622,7 +645,7 @@
* @param nanoAppId the ID of the nanoapp that aborted
* @param abortCode the nanoapp specific abort code
*/
- /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
+ void onNanoAppAborted(long nanoAppId, int abortCode) {
invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
Supplier<Intent> supplier =
@@ -632,18 +655,19 @@
}
/**
- * @param intent the PendingIntent to compare to
+ * @param intent the PendingIntent to compare to
* @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
* @return true if the given PendingIntent is currently registered, false otherwise
*/
- /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
+ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
PendingIntent pendingIntent;
long intentNanoAppId;
synchronized (this) {
pendingIntent = mPendingIntentRequest.getPendingIntent();
intentNanoAppId = mPendingIntentRequest.getNanoAppId();
}
- return (pendingIntent != null) && pendingIntent.equals(intent)
+ return (pendingIntent != null)
+ && pendingIntent.equals(intent)
&& intentNanoAppId == nanoAppId;
}
@@ -652,9 +676,9 @@
*
* @throws RemoteException if the client process already died
*/
- /* package */ void attachDeathRecipient() throws RemoteException {
- if (mCallbackInterface != null) {
- mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ void attachDeathRecipient() throws RemoteException {
+ if (mContextHubClientCallback != null) {
+ mContextHubClientCallback.asBinder().linkToDeath(this, 0 /* flags */);
}
}
@@ -664,7 +688,7 @@
* @param permissions list of permissions to check
* @return true if the client has all of the permissions granted
*/
- /* package */ boolean hasPermissions(List<String> permissions) {
+ boolean hasPermissions(List<String> permissions) {
for (String permission : permissions) {
if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
return false;
@@ -678,10 +702,10 @@
*
* @param permissions list of permissions covering data the client is about to receive
* @param noteMessage message that should be noted alongside permissions attribution to
- * facilitate debugging
+ * facilitate debugging
* @return true if client has ability to use all of the provided permissions
*/
- /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+ boolean notePermissions(List<String> permissions, String noteMessage) {
for (String permission : permissions) {
int opCode = AppOpsManager.permissionToOpCode(permission);
if (opCode != AppOpsManager.OP_NONE) {
@@ -691,8 +715,14 @@
return false;
}
} catch (SecurityException e) {
- Log.e(TAG, "SecurityException: noteOp for pkg " + mPackage + " opcode "
- + opCode + ": " + e.getMessage());
+ Log.e(
+ TAG,
+ "SecurityException: noteOp for pkg "
+ + mPackage
+ + " opcode "
+ + opCode
+ + ": "
+ + e.getMessage());
return false;
}
}
@@ -704,7 +734,7 @@
/**
* @return true if the client is a PendingIntent client that has been cancelled.
*/
- /* package */ boolean isPendingIntentCancelled() {
+ boolean isPendingIntentCancelled() {
return mIsPendingIntentCancelled.get();
}
@@ -712,7 +742,7 @@
* Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
* period.
*/
- /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+ void handleAuthStateTimerExpiry(long nanoAppId) {
AuthStateDenialTimer timer;
synchronized (mMessageChannelNanoappIdMap) {
timer = mNappToAuthTimerMap.remove(nanoAppId);
@@ -720,7 +750,8 @@
if (timer != null) {
updateNanoAppAuthState(
- nanoAppId, Collections.emptyList() /* nanoappPermissions */,
+ nanoAppId,
+ Collections.emptyList() /* nanoappPermissions */,
true /* gracePeriodExpired */);
}
}
@@ -755,8 +786,10 @@
* it should transition to denied
* @return the latest auth state as of the completion of this method.
*/
- /* package */ int updateNanoAppAuthState(
- long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired,
+ int updateNanoAppAuthState(
+ long nanoAppId,
+ List<String> nanoappPermissions,
+ boolean gracePeriodExpired,
boolean forceDenied) {
int curAuthState;
int newAuthState;
@@ -834,13 +867,17 @@
* @param consumer the consumer specifying the callback to invoke
*/
private synchronized void invokeCallback(CallbackConsumer consumer) {
- if (mCallbackInterface != null) {
+ if (mContextHubClientCallback != null) {
try {
acquireWakeLock();
- consumer.accept(mCallbackInterface);
+ consumer.accept(mContextHubClientCallback);
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException while invoking client callback (host endpoint ID = "
- + mHostEndPointId + ")", e);
+ Log.e(
+ TAG,
+ "RemoteException while invoking client callback (host endpoint ID = "
+ + mHostEndPointId
+ + ")",
+ e);
}
}
}
@@ -879,20 +916,20 @@
*/
private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
if (mPendingIntentRequest.hasPendingIntent()) {
- doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get());
+ doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
}
}
/**
* Sends an intent to any existing PendingIntent
*
- * @param supplier method to create the extra Intent
+ * @param supplier method to create the extra Intent
* @param nanoAppId the ID of the nanoapp which this event is for
*/
private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) {
if (mPendingIntentRequest.hasPendingIntent()
&& mPendingIntentRequest.getNanoAppId() == nanoAppId) {
- doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get());
+ doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get(), this);
}
}
@@ -902,7 +939,11 @@
* @param pendingIntent the PendingIntent
* @param intent the extra Intent data
*/
- private void doSendPendingIntent(PendingIntent pendingIntent, Intent intent) {
+ @VisibleForTesting
+ void doSendPendingIntent(
+ PendingIntent pendingIntent,
+ Intent intent,
+ PendingIntent.OnFinished onFinishedCallback) {
try {
String requiredPermission = Manifest.permission.ACCESS_CONTEXT_HUB;
acquireWakeLock();
@@ -910,7 +951,7 @@
mContext,
/* code= */ 0,
intent,
- /* onFinished= */ this,
+ /* onFinished= */ onFinishedCallback,
/* handler= */ null,
requiredPermission,
/* options= */ null);
@@ -934,13 +975,11 @@
return mRegistered;
}
- /**
- * Invoked when a client exits either explicitly or by binder death.
- */
+ /** Invoked when a client exits either explicitly or by binder death. */
private synchronized void onClientExit() {
- if (mCallbackInterface != null) {
- mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
- mCallbackInterface = null;
+ if (mContextHubClientCallback != null) {
+ mContextHubClientCallback.asBinder().unlinkToDeath(this, 0 /* flags */);
+ mContextHubClientCallback = null;
}
// The client is only unregistered and cleared when there is NOT any PendingIntent
if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
@@ -1056,7 +1095,7 @@
Binder.withCleanCallingIdentity(
() -> {
synchronized (mWakeLock) {
- if (mIsWakeLockActive) {
+ if (mIsWakelockUsable) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
}
}
@@ -1092,7 +1131,7 @@
Binder.withCleanCallingIdentity(
() -> {
synchronized (mWakeLock) {
- mIsWakeLockActive = false;
+ mIsWakelockUsable = false;
while (mWakeLock.isHeld()) {
try {
mWakeLock.release();
diff --git a/services/core/java/com/android/server/location/contexthub/TEST_MAPPING b/services/core/java/com/android/server/location/contexthub/TEST_MAPPING
new file mode 100644
index 0000000..2f6aa53
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/TEST_MAPPING
@@ -0,0 +1,26 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.location.contexthub."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
+ "imports": [
+ {
+ "path": "frameworks/base/services/tests/servicestests/src/com/android/server/location/contexthub"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index 2bdeab4..f144cf8 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -44,7 +44,6 @@
public class BiometricDeferredQueue {
private static final String TAG = "BiometricDeferredQueue";
- @NonNull private final Context mContext;
@NonNull private final SyntheticPasswordManager mSpManager;
@NonNull private final Handler mHandler;
@Nullable private FingerprintManager mFingerprintManager;
@@ -131,9 +130,7 @@
mFaceResetLockoutTask = null;
};
- BiometricDeferredQueue(@NonNull Context context, @NonNull SyntheticPasswordManager spManager,
- @NonNull Handler handler) {
- mContext = context;
+ BiometricDeferredQueue(@NonNull SyntheticPasswordManager spManager, @NonNull Handler handler) {
mSpManager = spManager;
mHandler = handler;
mPendingResetLockoutsForFingerprint = new ArrayList<>();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f4ad750..c5f73625 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -67,7 +67,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.V1_0.IAuthSecret;
@@ -238,7 +237,6 @@
@VisibleForTesting
protected final SyntheticPasswordManager mSpManager;
- private final KeyStore mKeyStore;
private final java.security.KeyStore mJavaKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private ManagedProfilePasswordCache mManagedProfilePasswordCache;
@@ -570,7 +568,6 @@
protected LockSettingsService(Injector injector) {
mInjector = injector;
mContext = injector.getContext();
- mKeyStore = injector.getKeyStore();
mJavaKeyStore = injector.getJavaKeyStore();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler(injector.getServiceThread());
@@ -595,7 +592,7 @@
mSpManager = injector.getSyntheticPasswordManager(mStorage);
mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
- mBiometricDeferredQueue = new BiometricDeferredQueue(mContext, mSpManager, mHandler);
+ mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
mStorage);
@@ -637,7 +634,6 @@
}
private void showEncryptionNotificationForProfile(UserHandle user, String reason) {
- Resources r = mContext.getResources();
CharSequence title = getEncryptionNotificationTitle();
CharSequence message = getEncryptionNotificationMessage();
CharSequence detail = getEncryptionNotificationDetail();
@@ -657,7 +653,7 @@
PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
- Slog.d(TAG, String.format("showing encryption notification, user: %d; reason: %s",
+ Slog.d(TAG, TextUtils.formatSimple("showing encryption notification, user: %d; reason: %s",
user.getIdentifier(), reason));
showEncryptionNotification(user, title, message, detail, intent);
@@ -839,7 +835,7 @@
if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet
}
- checkWritePermission(UserHandle.USER_SYSTEM);
+ checkWritePermission();
mHasSecureLockScreen = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
@@ -979,7 +975,7 @@
}
}
- private final void checkWritePermission(int userId) {
+ private final void checkWritePermission() {
mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
}
@@ -987,7 +983,7 @@
mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
}
- private final void checkPasswordHavePermission(int userId) {
+ private final void checkPasswordHavePermission() {
if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet
}
@@ -1056,7 +1052,7 @@
@Override
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
LockscreenCredential profileUserPassword) {
- checkWritePermission(userId);
+ checkWritePermission();
if (!mHasSecureLockScreen
&& profileUserPassword != null
&& profileUserPassword.getType() != CREDENTIAL_TYPE_NONE) {
@@ -1103,19 +1099,19 @@
@Override
public void setBoolean(String key, boolean value, int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
mStorage.setBoolean(key, value, userId);
}
@Override
public void setLong(String key, long value, int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
mStorage.setLong(key, value, userId);
}
@Override
public void setString(String key, String value, int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
mStorage.setString(key, value, userId);
}
@@ -1154,7 +1150,7 @@
*/
@Override
public int getCredentialType(int userId) {
- checkPasswordHavePermission(userId);
+ checkPasswordHavePermission();
return getCredentialTypeInternal(userId);
}
@@ -1967,7 +1963,7 @@
@Override
public void resetKeyStore(int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
List<Integer> profileUserIds = new ArrayList<>();
List<LockscreenCredential> profileUserDecryptedPasswords = new ArrayList<>();
@@ -2275,7 +2271,7 @@
@Override
public void requireStrongAuth(int strongAuthReason, int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
mStrongAuth.requireStrongAuth(strongAuthReason, userId);
}
@@ -2293,7 +2289,7 @@
@Override
public void userPresent(int userId) {
- checkWritePermission(userId);
+ checkWritePermission();
mStrongAuth.reportUnlock(userId);
}
@@ -2321,9 +2317,9 @@
final int origPid = Binder.getCallingPid();
final int origUid = Binder.getCallingUid();
+ Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
// The original identity is an opaque integer.
final long origId = Binder.clearCallingIdentity();
- Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
try {
final LockSettingsShellCommand command =
new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
@@ -2847,7 +2843,7 @@
synchronized (mSpManager) {
disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
for (long handle : mSpManager.getPendingTokensForUser(userId)) {
- Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId));
+ Slog.i(TAG, TextUtils.formatSimple("activateEscrowTokens: %x %d ", handle, userId));
mSpManager.createTokenBasedProtector(handle, sp, userId);
}
}
@@ -3008,14 +3004,14 @@
pw.println("User " + userId);
pw.increaseIndent();
synchronized (mSpManager) {
- pw.println(String.format("LSKF-based SP protector ID: %x",
+ pw.println(TextUtils.formatSimple("LSKF-based SP protector ID: %x",
getCurrentLskfBasedProtectorId(userId)));
- pw.println(String.format("LSKF last changed: %s (previous protector: %x)",
+ pw.println(TextUtils.formatSimple("LSKF last changed: %s (previous protector: %x)",
timestampToString(getLong(LSKF_LAST_CHANGED_TIME_KEY, 0, userId)),
getLong(PREV_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId)));
}
try {
- pw.println(String.format("SID: %x",
+ pw.println(TextUtils.formatSimple("SID: %x",
getGateKeeperService().getSecureUserId(userId)));
} catch (RemoteException e) {
// ignore.
@@ -3026,7 +3022,7 @@
pw.println("CredentialType: " + credentialTypeToString(
getCredentialTypeInternal(userId)));
pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabledInternal(userId));
- pw.println(String.format("Metrics: %s",
+ pw.println(TextUtils.formatSimple("Metrics: %s",
getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
pw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index e5b50362..db036b0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -17,7 +17,6 @@
package com.android.server.locksettings;
import static android.content.Context.USER_SERVICE;
-import static android.text.TextUtils.formatSimple;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -437,7 +436,7 @@
}
private File getSyntheticPasswordStateFileForUser(int userId, long protectorId, String name) {
- String fileName = formatSimple("%016x.%s", protectorId, name);
+ String fileName = TextUtils.formatSimple("%016x.%s", protectorId, name);
return new File(getSyntheticPasswordDirectoryForUser(userId), fileName);
}
@@ -643,13 +642,13 @@
final UserManager um = UserManager.get(mContext);
for (UserInfo user : um.getUsers()) {
File userPath = getSyntheticPasswordDirectoryForUser(user.id);
- pw.println(String.format("User %d [%s]:", user.id, userPath));
+ pw.println(TextUtils.formatSimple("User %d [%s]:", user.id, userPath));
pw.increaseIndent();
File[] files = userPath.listFiles();
if (files != null) {
Arrays.sort(files);
for (File file : files) {
- pw.println(String.format("%6d %s %s", file.length(),
+ pw.println(TextUtils.formatSimple("%6d %s %s", file.length(),
LockSettingsService.timestampToString(file.lastModified()),
file.getName()));
}
diff --git a/services/core/java/com/android/server/locksettings/PasswordSlotManager.java b/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
index 17aca15..21fb403 100644
--- a/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
+++ b/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
@@ -72,8 +72,6 @@
/**
* Notify the manager of which slots are definitively in use by the current OS image.
- *
- * @throws RuntimeException
*/
public void refreshActiveSlots(Set<Integer> activeSlots) throws RuntimeException {
if (mSlotMap == null) {
@@ -103,8 +101,6 @@
/**
* Mark the given slot as in use by the current OS image.
- *
- * @throws RuntimeException
*/
public void markSlotInUse(int slot) throws RuntimeException {
ensureSlotMapLoaded();
@@ -117,8 +113,6 @@
/**
* Mark the given slot as no longer in use by the current OS image.
- *
- * @throws RuntimeException
*/
public void markSlotDeleted(int slot) throws RuntimeException {
ensureSlotMapLoaded();
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowKeyStoreManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowKeyStoreManager.java
index da29368..41cdb42 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowKeyStoreManager.java
@@ -64,9 +64,9 @@
private SecretKey getKeyStoreEncryptionKeyLocked() {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
- KeyStore.LoadStoreParameter loadStoreParameter = null;
// Load from the specific namespace if keystore2 is enabled.
- loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
+ KeyStore.LoadStoreParameter loadStoreParameter =
+ new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
keyStore.load(loadStoreParameter);
return (SecretKey) keyStore.getKey(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME,
null);
@@ -86,9 +86,9 @@
synchronized (mKeyStoreLock) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
- KeyStore.LoadStoreParameter loadStoreParameter = null;
// Load from the specific namespace if keystore2 is enabled.
- loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
+ KeyStore.LoadStoreParameter loadStoreParameter =
+ new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
keyStore.load(loadStoreParameter);
keyStore.deleteEntry(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME);
} catch (IOException | GeneralSecurityException e) {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index a931844..2a6ae44 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -23,6 +23,7 @@
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
@@ -301,7 +302,7 @@
// Treat this as a success so we don't migrate again.
return true;
} else {
- Slog.e(TAG, String.format("Failed to migrate key: %d", err));
+ Slog.e(TAG, TextUtils.formatSimple("Failed to migrate key: %d", err));
return false;
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 5b75b6a..f1afb96 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -36,6 +36,7 @@
import android.security.Scrypt;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -590,7 +591,7 @@
// Remove potential persistent state (in RPMB), to prevent them from accumulating and
// causing problems.
try {
- gatekeeper.clearSecureUserId(fakeUid(userId));
+ gatekeeper.clearSecureUserId(fakeUserId(userId));
} catch (RemoteException ignore) {
Slog.w(TAG, "Failed to clear SID from gatekeeper");
}
@@ -800,13 +801,13 @@
// In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
// to prevent them from accumulating and causing problems.
try {
- gatekeeper.clearSecureUserId(fakeUid(userId));
+ gatekeeper.clearSecureUserId(fakeUserId(userId));
} catch (RemoteException ignore) {
Slog.w(TAG, "Failed to clear SID from gatekeeper");
}
GateKeeperResponse response;
try {
- response = gatekeeper.enroll(fakeUid(userId), null, null,
+ response = gatekeeper.enroll(fakeUserId(userId), null, null,
stretchedLskfToGkPassword(stretchedLskf));
} catch (RemoteException e) {
throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
@@ -840,7 +841,7 @@
GateKeeperResponse response;
try {
- response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId),
+ response = gatekeeper.verifyChallenge(fakeUserId(persistentData.userId),
0 /* challenge */, pwd.passwordHandle,
stretchedLskfToGkPassword(stretchedLskf));
} catch (RemoteException e) {
@@ -1029,7 +1030,7 @@
userId));
if (!credential.checkAgainstStoredType(pwd.credentialType)) {
- Slog.e(TAG, String.format("Credential type mismatch: expected %d actual %d",
+ Slog.e(TAG, TextUtils.formatSimple("Credential type mismatch: expected %d actual %d",
pwd.credentialType, credential.getType()));
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
@@ -1059,7 +1060,7 @@
byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
GateKeeperResponse response;
try {
- response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
+ response = gatekeeper.verifyChallenge(fakeUserId(userId), 0L,
pwd.passwordHandle, gkPassword);
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
@@ -1072,7 +1073,7 @@
if (response.getShouldReEnroll()) {
GateKeeperResponse reenrollResponse;
try {
- reenrollResponse = gatekeeper.enroll(fakeUid(userId),
+ reenrollResponse = gatekeeper.enroll(fakeUserId(userId),
pwd.passwordHandle, gkPassword, gkPassword);
} catch (RemoteException e) {
Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
@@ -1452,8 +1453,8 @@
return result;
}
- private int fakeUid(int uid) {
- return 100000 + uid;
+ private int fakeUserId(int userId) {
+ return 100000 + userId;
}
protected static byte[] secureRandom(int length) {
@@ -1466,7 +1467,7 @@
}
private String getProtectorKeyAlias(long protectorId) {
- return String.format("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
+ return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
}
private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index f32af54..7009a41 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -75,7 +75,6 @@
"com.android.server.locksettings.recoverablekeystore/platform/";
private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
- private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
private static final String KEY_WRAP_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH_BITS = 128;
// Only used for checking if a key is usable
@@ -184,7 +183,6 @@
invalidatePlatformKey(userId, generationId);
nextId = generationId + 1;
}
- generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
generateAndLoadKey(userId, nextId);
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java
index be35b50..7d5fd65 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/CleanupManager.java
@@ -35,7 +35,6 @@
public class CleanupManager {
private static final String TAG = "CleanupManager";
- private final Context mContext;
private final UserManager mUserManager;
private final RecoverableKeyStoreDb mDatabase;
private final RecoverySnapshotStorage mSnapshotStorage;
@@ -54,7 +53,6 @@
RecoverableKeyStoreDb recoverableKeyStoreDb,
ApplicationKeyStorage applicationKeyStorage) {
return new CleanupManager(
- context,
snapshotStorage,
recoverableKeyStoreDb,
UserManager.get(context),
@@ -63,12 +61,10 @@
@VisibleForTesting
CleanupManager(
- Context context,
RecoverySnapshotStorage snapshotStorage,
RecoverableKeyStoreDb recoverableKeyStoreDb,
UserManager userManager,
ApplicationKeyStorage applicationKeyStorage) {
- mContext = context;
mSnapshotStorage = snapshotStorage;
mDatabase = recoverableKeyStoreDb;
mUserManager = userManager;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7d12ede..ed8d852 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -335,8 +335,8 @@
@Override // Binder call
public void stopActiveProjection() {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
- != PackageManager.PERMISSION_GRANTED) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add "
+ "projection callbacks");
}
@@ -393,9 +393,14 @@
if (!isValidMediaProjection(projection)) {
throw new SecurityException("Invalid media projection");
}
- LocalServices.getService(
+ if (!LocalServices.getService(
WindowManagerInternal.class).setContentRecordingSession(
- incomingSession);
+ incomingSession)) {
+ // Unable to start mirroring, so tear down this projection.
+ if (mProjectionGrant != null) {
+ mProjectionGrant.stop();
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index f5910fa..718756f 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.IntDef;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
@@ -30,6 +31,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* The {@link PackageManagerService} maintains some {@link CrossProfileIntentFilter}s for each user.
@@ -41,13 +44,52 @@
private static final String ATTR_FLAGS = "flags";
private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
private static final String ATTR_FILTER = "filter";
+ private static final String ATTR_ACCESS_CONTROL = "accessControl";
private static final String TAG = "CrossProfileIntentFilter";
+ /**
+ * AccessControlLevel provides level of access for user to create/modify
+ * {@link CrossProfileIntentFilter} in {@link com.android.server.pm.Settings}.
+ * Each AccessControlLevel have value assigned, the higher the value
+ * implies higher restriction for creation/modification.
+ * AccessControlLevel allows us to protect against malicious changes in user's
+ * {@link CrossProfileIntentFilter}s, which might add/remove {@link CrossProfileIntentFilter}
+ * leading to unprecedented results.
+ */
+ @IntDef(prefix = {"ACCESS_LEVEL_"}, value = {
+ ACCESS_LEVEL_ALL,
+ ACCESS_LEVEL_SYSTEM,
+ ACCESS_LEVEL_SYSTEM_ADD_ONLY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessControlLevel {
+ }
+
+ /**
+ * ACCESS_LEVEL_ALL signifies that irrespective of user we would allow
+ * access(addition/modification/removal) for CrossProfileIntentFilter.
+ * This is the default access control level.
+ */
+ public static final int ACCESS_LEVEL_ALL = 0;
+
+ /**
+ * ACCESS_LEVEL_SYSTEM signifies that only system/root user would be able to
+ * access(addition/modification/removal) CrossProfileIntentFilter.
+ */
+ public static final int ACCESS_LEVEL_SYSTEM = 10;
+
+ /**
+ * ACCESS_LEVEL_SYSTEM_ADD_ONLY signifies that only system/root user would be able to add
+ * CrossProfileIntentFilter but not modify/remove. Once added, it cannot be modified or removed.
+ */
+ public static final int ACCESS_LEVEL_SYSTEM_ADD_ONLY = 20;
+
// If the intent matches the IntentFilter, then it can be forwarded to this userId.
final int mTargetUserId;
final String mOwnerPackage; // packageName of the app.
final int mFlags;
+ final int mAccessControlLevel;
// The cache for snapshots, so they are not rebuilt if the base object has not
// changed.
@@ -65,10 +107,16 @@
CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
int flags) {
+ this(filter, ownerPackage, targetUserId, flags, ACCESS_LEVEL_ALL);
+ }
+
+ CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
+ int flags, @AccessControlLevel int accessControlLevel) {
super(filter);
mTargetUserId = targetUserId;
mOwnerPackage = ownerPackage;
mFlags = flags;
+ mAccessControlLevel = accessControlLevel;
mSnapshot = makeCache();
}
@@ -77,12 +125,18 @@
this(filter.mFilter, ownerPackage, targetUserId, flags);
}
+ CrossProfileIntentFilter(WatchedIntentFilter filter, String ownerPackage, int targetUserId,
+ int flags, @AccessControlLevel int accessControlLevel) {
+ this(filter.mFilter, ownerPackage, targetUserId, flags, accessControlLevel);
+ }
+
// Copy constructor used only to create a snapshot.
private CrossProfileIntentFilter(CrossProfileIntentFilter f) {
super(f);
mTargetUserId = f.mTargetUserId;
mOwnerPackage = f.mOwnerPackage;
mFlags = f.mFlags;
+ mAccessControlLevel = f.mAccessControlLevel;
mSnapshot = new SnapshotCache.Sealed();
}
@@ -98,9 +152,14 @@
return mOwnerPackage;
}
+ public @AccessControlLevel int getAccessControlLevel() {
+ return mAccessControlLevel;
+ }
+
CrossProfileIntentFilter(TypedXmlPullParser parser) throws XmlPullParserException, IOException {
mTargetUserId = parser.getAttributeInt(null, ATTR_TARGET_USER_ID, UserHandle.USER_NULL);
mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, "");
+ mAccessControlLevel = parser.getAttributeInt(null, ATTR_ACCESS_CONTROL, ACCESS_LEVEL_ALL);
mFlags = parser.getAttributeInt(null, ATTR_FLAGS, 0);
mSnapshot = makeCache();
@@ -151,6 +210,7 @@
serializer.attributeInt(null, ATTR_TARGET_USER_ID, mTargetUserId);
serializer.attributeInt(null, ATTR_FLAGS, mFlags);
serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage);
+ serializer.attributeInt(null, ATTR_ACCESS_CONTROL, mAccessControlLevel);
serializer.startTag(null, ATTR_FILTER);
mFilter.writeToXml(serializer);
serializer.endTag(null, ATTR_FILTER);
@@ -165,7 +225,8 @@
boolean equalsIgnoreFilter(CrossProfileIntentFilter other) {
return mTargetUserId == other.mTargetUserId
&& mOwnerPackage.equals(other.mOwnerPackage)
- && mFlags == other.mFlags;
+ && mFlags == other.mFlags
+ && mAccessControlLevel == other.mAccessControlLevel;
}
public CrossProfileIntentFilter snapshot() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8b40f65..72f3850 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3350,6 +3350,12 @@
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
int callingUid = Binder.getCallingUid();
enforceOwnerRights(snapshot, ownerPackage, callingUid);
+
+ // Verifying that current calling uid should be able to add {@link CrossProfileIntentFilter}
+ // for source and target user
+ mUserManager.enforceCrossProfileIntentFilterAccess(sourceUserId, targetUserId, callingUid,
+ /* addCrossProfileIntentFilter */ true);
+
PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
if (intentFilter.countActions() == 0) {
@@ -3358,7 +3364,8 @@
}
synchronized (mLock) {
CrossProfileIntentFilter newFilter = new CrossProfileIntentFilter(intentFilter,
- ownerPackage, targetUserId, flags);
+ ownerPackage, targetUserId, flags, mUserManager
+ .getCrossProfileIntentFilterAccessControl(sourceUserId, targetUserId));
CrossProfileIntentResolver resolver =
mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
ArrayList<CrossProfileIntentFilter> existing = resolver.findFilters(intentFilter);
@@ -4572,7 +4579,11 @@
ArraySet<CrossProfileIntentFilter> set =
new ArraySet<>(resolver.filterSet());
for (CrossProfileIntentFilter filter : set) {
- if (filter.getOwnerPackage().equals(ownerPackage)) {
+ //Only remove if calling user is allowed based on access control of
+ // {@link CrossProfileIntentFilter}
+ if (filter.getOwnerPackage().equals(ownerPackage)
+ && mUserManager.isCrossProfileIntentFilterAccessible(sourceUserId,
+ filter.mTargetUserId, /* addCrossProfileIntentFilter */ false)) {
resolver.removeFilter(filter);
}
}
@@ -6412,6 +6423,8 @@
@Override
public void setVisibilityLogging(String packageName, boolean enable) {
+ PackageManagerServiceUtils.enforceSystemOrRootOrShell(
+ "Only the system or shell can set visibility logging.");
final PackageStateInternal packageState =
snapshot().getPackageStateInternal(packageName);
if (packageState == null) {
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index a574fc9..8acdb0e 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -31,6 +31,7 @@
private final SparseBooleanArray mSufficientVerifierUids;
private final SparseBooleanArray mRequiredVerifierUids;
+ private final SparseBooleanArray mUnrespondedRequiredVerifierUids;
private boolean mSufficientVerificationComplete;
@@ -52,6 +53,7 @@
mVerifyingSession = verifyingSession;
mSufficientVerifierUids = new SparseBooleanArray();
mRequiredVerifierUids = new SparseBooleanArray();
+ mUnrespondedRequiredVerifierUids = new SparseBooleanArray();
mRequiredVerificationComplete = false;
mRequiredVerificationPassed = true;
mExtendedTimeout = false;
@@ -64,6 +66,7 @@
/** Add the user ID of the required package verifier. */
void addRequiredVerifierUid(int uid) {
mRequiredVerifierUids.put(uid, true);
+ mUnrespondedRequiredVerifierUids.put(uid, true);
}
/** Returns true if the uid a required verifier. */
@@ -103,8 +106,8 @@
mRequiredVerificationPassed = false;
}
- mRequiredVerifierUids.delete(uid);
- if (mRequiredVerifierUids.size() == 0) {
+ mUnrespondedRequiredVerifierUids.delete(uid);
+ if (mUnrespondedRequiredVerifierUids.size() == 0) {
mRequiredVerificationComplete = true;
}
return true;
@@ -129,7 +132,7 @@
* Mark the session as passed required verification.
*/
void passRequiredVerification() {
- if (mRequiredVerifierUids.size() > 0) {
+ if (mUnrespondedRequiredVerifierUids.size() > 0) {
throw new RuntimeException("Required verifiers still present.");
}
mRequiredVerificationPassed = true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0c1c265..a0335e8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1650,11 +1650,40 @@
}
int currentUser = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
- // TODO(b/179163496): should return true for profile users of the current user as well
return currentUser == userId;
}
@Override
+ public boolean isUserVisible(@UserIdInt int userId) {
+ int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId
+ && !hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
+ throw new SecurityException("Caller from user " + callingUserId + " needs MANAGE_USERS "
+ + "or INTERACT_ACROSS_USERS permission to check if another user (" + userId
+ + ") is visible");
+ }
+
+ // First check current foreground user (on main display)
+ int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
+
+ if (currentUserId == userId) {
+ return true;
+ }
+
+ // Then profiles of current user
+ // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead
+ if (isProfile(userId)) {
+ int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId));
+ if (parentId == currentUserId) {
+ return isUserRunning(userId);
+ }
+ }
+
+ // TODO(b/239824814): support background users on secondary display (and their profiles)
+ return false;
+ }
+
+ @Override
public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
if (!hasQueryOrCreateUsersPermission()
@@ -1866,6 +1895,103 @@
return mLocalService.exists(userId);
}
+ /**
+ * Returns user's {@link CrossProfileIntentFilter.AccessControlLevel}, which is derived from
+ * {@link UserTypeDetails}. If user does not have defined their access control level,
+ * returns default {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
+ */
+ private @CrossProfileIntentFilter.AccessControlLevel int
+ getCrossProfileIntentFilterAccessControl(@UserIdInt int userId) {
+ final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
+ return userTypeDetails != null ? userTypeDetails.getCrossProfileIntentFilterAccessControl()
+ : CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
+ }
+
+ /**
+ * Verifies if calling user is allowed to access {@link CrossProfileIntentFilter} between given
+ * source and target user.
+ * @param sourceUserId userId for which CrossProfileIntentFilter would be configured
+ * @param targetUserId target user where we can resolve given intent filter
+ * @param callingUid user accessing api
+ * @param addCrossProfileIntentFilter if the operation is addition or not.
+ * @throws SecurityException is calling user is not allowed to access.
+ */
+ public void enforceCrossProfileIntentFilterAccess(
+ int sourceUserId, int targetUserId,
+ int callingUid, boolean addCrossProfileIntentFilter) {
+ if (!isCrossProfileIntentFilterAccessible(sourceUserId, targetUserId,
+ addCrossProfileIntentFilter)) {
+ throw new SecurityException("CrossProfileIntentFilter cannot be accessed by user "
+ + callingUid);
+ }
+ }
+
+ /**
+ * Checks if {@link CrossProfileIntentFilter} can be accessed by calling user for given source
+ * and target user. There are following rules of access
+ * 1. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL},
+ * irrespective of user we would allow access(addition/modification/removal)
+ * 2. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM},
+ * only system/root user would be able to access(addition/modification/removal)
+ * 3. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM_ADD_ONLY},
+ * only system/root user would be able to add but not modify/remove. Once added, it cannot be
+ * modified or removed
+ * @param sourceUserId userId for which CrossProfileIntentFilter would be configured
+ * @param targetUserId target user where we can resolve given intent filter
+ * @param addCrossProfileIntentFilter if the operation is addition or not.
+ * @return true if {@link CrossProfileIntentFilter} can be accessed by calling user
+ */
+ public boolean isCrossProfileIntentFilterAccessible(int sourceUserId, int targetUserId,
+ boolean addCrossProfileIntentFilter) {
+ int effectiveAccessControl =
+ getCrossProfileIntentFilterAccessControl(sourceUserId, targetUserId);
+
+ /*
+ For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM}, if accessing user is not
+ system or root disallowing access to {@link CrossProfileIntentFilter}
+ */
+ if (CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM == effectiveAccessControl
+ && !PackageManagerServiceUtils.isSystemOrRoot()) {
+ return false;
+ }
+
+ /*
+ For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM_ADD_ONLY}, allowing only
+ system user to add {@link CrossProfileIntentFilter}. All users(including system) are
+ disallowed to modify/remove.
+ */
+ if (CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM_ADD_ONLY == effectiveAccessControl
+ && (!addCrossProfileIntentFilter || !PackageManagerServiceUtils.isSystemOrRoot())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns {@link CrossProfileIntentFilter.AccessControlLevel}
+ * that should be assigned to {@link CrossProfileIntentFilter}
+ * computed from source user's and target user's
+ * {@link CrossProfileIntentFilter.AccessControlLevel}.
+ * The Access Level is configured per {@link CrossProfileIntentFilter} and its property of edge
+ * between source and target user e.g. for all {@link CrossProfileIntentFilter}s configured
+ * between Primary user and Clone profile should have access level of
+ * {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM} which is driven by highest
+ * access value from source or target. The higher value means higher restrictions.
+ * @param sourceUserId userId of source user for whom CrossProfileIntentFilter will be stored
+ * @param targetUserId userId of target user for whom Cross Profile access would be allowed
+ * @return least privileged {@link CrossProfileIntentFilter.AccessControlLevel} from source or
+ * target user.
+ */
+ public @CrossProfileIntentFilter.AccessControlLevel int
+ getCrossProfileIntentFilterAccessControl(int sourceUserId, int targetUserId) {
+ int sourceAccessControlLevel,
+ targetAccessControlLevel, effectiveAccessControl;
+ sourceAccessControlLevel = getCrossProfileIntentFilterAccessControl(sourceUserId);
+ targetAccessControlLevel = getCrossProfileIntentFilterAccessControl(targetUserId);
+ effectiveAccessControl = Math.max(sourceAccessControlLevel, targetAccessControlLevel);
+ return effectiveAccessControl;
+ }
+
@Override
public void setUserName(@UserIdInt int userId, String name) {
checkManageUsersPermission("rename users");
@@ -4556,34 +4682,31 @@
}
final List<UserInfo> users = getUsersInternal(true, true, true);
final int size = users.size();
- for (int idx = 0; idx < size; idx++) {
- final UserInfo user = users.get(idx);
- if (user.id == UserHandle.USER_SYSTEM) {
- // Skip user 0. It's not interesting. We already know it exists, is running, and (if
- // we know the device configuration) its userType.
- continue;
+ if (size > 1) {
+ for (int idx = 0; idx < size; idx++) {
+ final UserInfo user = users.get(idx);
+ final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+ final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
+ ?
+ user.userType : null;
+
+ boolean isUserRunningUnlocked;
+ synchronized (mUserStates) {
+ isUserRunningUnlocked =
+ mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ }
+
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
+ user.id,
+ userTypeStandard,
+ userTypeCustom,
+ user.flags,
+ user.creationTime,
+ user.lastLoggedInTime,
+ isUserRunningUnlocked
+ ));
}
-
- final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
- final String userTypeCustom = (userTypeStandard ==
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) ?
- user.userType : null;
-
- boolean isUserRunningUnlocked;
- synchronized (mUserStates) {
- isUserRunningUnlocked =
- mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
- }
-
- data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
- user.id,
- userTypeStandard,
- userTypeCustom,
- user.flags,
- user.creationTime,
- user.lastLoggedInTime,
- isUserRunningUnlocked
- ));
}
return android.app.StatsManager.PULL_SUCCESS;
}
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 4aad1a7..2f3ca66 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -163,6 +163,14 @@
*/
private final boolean mIsCredentialSharableWithParent;
+ /**
+ * Denotes the default access control for {@link CrossProfileIntentFilter} of user profile.
+ *
+ * <p> Default value is {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
+ */
+ private final @CrossProfileIntentFilter.AccessControlLevel int
+ mCrossProfileIntentFilterAccessControl;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -174,7 +182,8 @@
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
- boolean isCredentialSharableWithParent) {
+ boolean isCredentialSharableWithParent,
+ @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -195,6 +204,7 @@
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
+ this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
}
/**
@@ -327,6 +337,16 @@
return mIsCredentialSharableWithParent;
}
+ /**
+ * Returning user's {@link CrossProfileIntentFilter.AccessControlLevel}. If not explicitly
+ * configured, default value is {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
+ * @return user's {@link CrossProfileIntentFilter.AccessControlLevel}
+ */
+ public @CrossProfileIntentFilter.AccessControlLevel int
+ getCrossProfileIntentFilterAccessControl() {
+ return mCrossProfileIntentFilterAccessControl;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -420,6 +440,8 @@
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
private boolean mIsMediaSharedWithParent = false;
private boolean mIsCredentialSharableWithParent = false;
+ private @CrossProfileIntentFilter.AccessControlLevel int
+ mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
public Builder setName(String name) {
mName = name;
@@ -520,6 +542,16 @@
}
/**
+ * Sets {@link CrossProfileIntentFilter.AccessControlLevel} for the user.
+ * @param accessControlLevel default access control for user
+ */
+ public Builder setCrossProfileIntentFilterAccessControl(
+ @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
+ mCrossProfileIntentFilterAccessControl = accessControlLevel;
+ return this;
+ }
+
+ /**
* Sets shared media property for the user.
* @param isCredentialSharableWithParent the value to be set, true or false
*/
@@ -571,7 +603,8 @@
mDefaultSecureSettings,
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
- mIsCredentialSharableWithParent);
+ mIsCredentialSharableWithParent,
+ mCrossProfileIntentFilterAccessControl);
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 0878da4..857a975 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -122,6 +122,8 @@
.setLabel(0)
.setDefaultRestrictions(null)
.setIsMediaSharedWithParent(true)
+ .setCrossProfileIntentFilterAccessControl(
+ CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
.setIsCredentialSharableWithParent(true);
}
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index af2b902..8582f54 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -23,11 +23,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
@@ -35,12 +32,11 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
+import android.view.View;
+import android.view.Window;
import android.view.WindowManager;
import com.android.internal.R;
@@ -48,49 +44,48 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
/**
* Defines behavior for handling interactions between power button events and fingerprint-related
* operations, for devices where the fingerprint sensor (side fps) lives on the power button.
*/
-public class SideFpsEventHandler {
+public class SideFpsEventHandler implements View.OnClickListener {
private static final int DEBOUNCE_DELAY_MILLIS = 500;
- private int getTapWaitForPowerDuration(Context context) {
- int tap = context.getResources().getInteger(
- R.integer.config_sidefpsEnrollPowerPressWindow);
- if (Build.isDebuggable()) {
- tap = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
- UserHandle.USER_CURRENT);
- }
- return tap;
- }
-
private static final String TAG = "SideFpsEventHandler";
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final PowerManager mPowerManager;
- @NonNull private final Supplier<AlertDialog.Builder> mDialogSupplier;
- @NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
-
- @Nullable private Dialog mDialog;
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final PowerManager mPowerManager;
+ @NonNull
+ private final AtomicBoolean mSideFpsEventHandlerReady;
+ private final int mDismissDialogTimeout;
+ @Nullable
+ private SideFpsToast mDialog;
private final Runnable mTurnOffDialog =
() -> {
dismissDialog("mTurnOffDialog");
};
-
- @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
-
private @BiometricStateListener.State int mBiometricState;
- private final int mTapWaitForPowerDuration;
private FingerprintManager mFingerprintManager;
+ private DialogProvider mDialogProvider;
+ private long mLastPowerPressTime;
- SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
- this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
+ SideFpsEventHandler(
+ Context context,
+ Handler handler,
+ PowerManager powerManager) {
+ this(context, handler, powerManager, (ctx) -> {
+ SideFpsToast dialog = new SideFpsToast(ctx);
+ dialog.getWindow()
+ .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ });
}
@VisibleForTesting
@@ -98,23 +93,13 @@
Context context,
Handler handler,
PowerManager powerManager,
- Supplier<AlertDialog.Builder> dialogSupplier) {
+ DialogProvider provider) {
mContext = context;
mHandler = handler;
mPowerManager = powerManager;
- mDialogSupplier = dialogSupplier;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
- mDialogDismissListener =
- (dialog) -> {
- if (mDialog == dialog) {
- if (mHandler != null) {
- mHandler.removeCallbacks(mTurnOffDialog);
- }
- mDialog = null;
- }
- };
-
+ mDialogProvider = provider;
// ensure dialog is dismissed if screen goes off for unrelated reasons
context.registerReceiver(
new BroadcastReceiver() {
@@ -127,7 +112,13 @@
}
},
new IntentFilter(Intent.ACTION_SCREEN_OFF));
- mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
+ mDismissDialogTimeout = context.getResources().getInteger(
+ R.integer.config_sideFpsToastTimeout);
+ }
+
+ @Override
+ public void onClick(View v) {
+ goToSleep(mLastPowerPressTime);
}
/**
@@ -165,8 +156,9 @@
Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
mHandler.removeCallbacks(mTurnOffDialog);
}
+ showDialog(eventTime, "Enroll Power Press");
+ mHandler.postDelayed(mTurnOffDialog, mDismissDialogTimeout);
});
- showDialog(eventTime, "Enroll Power Press");
return true;
case STATE_BP_AUTH:
return true;
@@ -176,54 +168,11 @@
}
}
- @NonNull
- private static Dialog showConfirmDialog(
- @NonNull AlertDialog.Builder dialogBuilder,
- @NonNull PowerManager powerManager,
- long eventTime,
- @BiometricStateListener.State int biometricState,
- @NonNull DialogInterface.OnDismissListener dismissListener) {
- final boolean enrolling = biometricState == STATE_ENROLLING;
- final int title =
- enrolling
- ? R.string.fp_power_button_enrollment_title
- : R.string.fp_power_button_bp_title;
- final int message =
- enrolling
- ? R.string.fp_power_button_enrollment_message
- : R.string.fp_power_button_bp_message;
- final int positiveText =
- enrolling
- ? R.string.fp_power_button_enrollment_positive_button
- : R.string.fp_power_button_bp_positive_button;
- final int negativeText =
- enrolling
- ? R.string.fp_power_button_enrollment_negative_button
- : R.string.fp_power_button_bp_negative_button;
-
- final Dialog confirmScreenOffDialog =
- dialogBuilder
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(
- positiveText,
- (dialog, which) -> {
- dialog.dismiss();
- powerManager.goToSleep(
- eventTime,
- PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
- 0 /* flags */);
- })
- .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
- .setOnDismissListener(dismissListener)
- .setCancelable(false)
- .create();
- confirmScreenOffDialog
- .getWindow()
- .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- confirmScreenOffDialog.show();
-
- return confirmScreenOffDialog;
+ private void goToSleep(long eventTime) {
+ mPowerManager.goToSleep(
+ eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ 0 /* flags */);
}
/**
@@ -247,7 +196,8 @@
if (fingerprintManager.isPowerbuttonFps()) {
fingerprintManager.registerBiometricStateListener(
new BiometricStateListener() {
- @Nullable private Runnable mStateRunnable = null;
+ @Nullable
+ private Runnable mStateRunnable = null;
@Override
public void onStateChanged(
@@ -281,13 +231,6 @@
public void onBiometricAction(
@BiometricStateListener.Action int action) {
Log.d(TAG, "onBiometricAction " + action);
- switch (action) {
- case BiometricStateListener.ACTION_SENSOR_TOUCH:
- mHandler.postDelayed(
- mTurnOffDialog,
- mTapWaitForPowerDuration);
- break;
- }
}
});
mSideFpsEventHandlerReady.set(true);
@@ -309,12 +252,13 @@
Log.d(TAG, "Ignoring show dialog");
return;
}
- mDialog =
- showConfirmDialog(
- mDialogSupplier.get(),
- mPowerManager,
- time,
- mBiometricState,
- mDialogDismissListener);
+ mDialog = mDialogProvider.provideDialog(mContext);
+ mLastPowerPressTime = time;
+ mDialog.show();
+ mDialog.setOnClickListener(this);
}
-}
+
+ interface DialogProvider {
+ SideFpsToast provideDialog(Context context);
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/SideFpsToast.java b/services/core/java/com/android/server/policy/SideFpsToast.java
new file mode 100644
index 0000000..db07467
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SideFpsToast.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.server.policy;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import com.android.internal.R;
+
+/**
+ * Toast for side fps. This is typically shown during enrollment
+ * when a user presses the power button.
+ *
+ * This dialog is used by {@link SideFpsEventHandler}
+ */
+public class SideFpsToast extends Dialog {
+
+ SideFpsToast(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.side_fps_toast);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final Window window = this.getWindow();
+ WindowManager.LayoutParams windowParams = window.getAttributes();
+ windowParams.dimAmount = 0;
+ windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+ windowParams.gravity = Gravity.BOTTOM;
+ window.setAttributes(windowParams);
+ }
+
+ /**
+ * Sets the onClickListener for the toast dialog.
+ * @param listener
+ */
+ public void setOnClickListener(View.OnClickListener listener) {
+ final Button turnOffScreen = findViewById(R.id.turn_off_screen);
+ if (turnOffScreen != null) {
+ turnOffScreen.setOnClickListener(listener);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f1dbad61..c04b195 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -30,6 +30,7 @@
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.accessibility.AccessibilityTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import static com.android.server.accessibility.AccessibilityTraceProto.ACCESSIBILITY_SERVICE;
import static com.android.server.accessibility.AccessibilityTraceProto.CALENDAR_TIME;
import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PARAMS;
@@ -116,6 +117,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* This class contains the accessibility related logic of the window manager.
@@ -2215,6 +2217,11 @@
try {
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(),
+ TimeUnit.NANOSECONDS)
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
mBuffer.writeTraceToFile(mTraceFile, proto);
} catch (IOException e) {
Slog.e(TAG, "Unable to write buffer to file", e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 135cf5d..c64e525 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -78,6 +78,11 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -336,6 +341,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
@@ -6734,7 +6740,7 @@
* @param windowPid The pid of the window input dispatching timed out on.
* @return True if input dispatching should be aborted.
*/
- public boolean inputDispatchingTimedOut(String reason, int windowPid) {
+ public boolean inputDispatchingTimedOut(TimeoutRecord timeoutRecord, int windowPid) {
ActivityRecord anrActivity;
WindowProcessController anrApp;
boolean blameActivityProcess;
@@ -6748,12 +6754,12 @@
if (blameActivityProcess) {
return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
anrActivity.shortComponentName, anrActivity.info.applicationInfo,
- shortComponentName, app, false, reason);
+ shortComponentName, app, false, timeoutRecord);
} else {
// In this case another process added windows using this activity token. So, we call the
// generic service input dispatch timed out method so that the right process is blamed.
long timeoutMillis = mAtmService.mAmInternal.inputDispatchingTimedOut(
- windowPid, false /* aboveSystem */, reason);
+ windowPid, false /* aboveSystem */, timeoutRecord);
return timeoutMillis <= 0;
}
}
@@ -8750,15 +8756,35 @@
/**
* Returns the min aspect ratio of this activity.
*/
- private float getMinAspectRatio() {
- return info.getMinAspectRatio(getRequestedOrientation());
+ float getMinAspectRatio() {
+ if (info.applicationInfo == null || !info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || (
+ info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+ && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation()))) {
+ return info.getMinAspectRatio();
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+ return Math.max(mLetterboxUiController.getSplitScreenAspectRatio(),
+ info.getMinAspectRatio());
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+ return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ info.getMinAspectRatio());
+ }
+
+ if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+ return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ info.getMinAspectRatio());
+ }
+ return info.getMinAspectRatio();
}
/**
* Returns true if the activity has maximum or minimum aspect ratio.
*/
private boolean hasFixedAspectRatio() {
- return info.hasFixedAspectRatio(getRequestedOrientation());
+ return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 1ed5a85..e0ac37a 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,6 +31,7 @@
import android.util.SparseArray;
import android.view.InputApplicationHandle;
+import com.android.internal.os.TimeoutRecord;
import com.android.server.am.ActivityManagerService;
import com.android.server.criticalevents.CriticalEventLog;
@@ -60,7 +61,8 @@
mService = service;
}
- void notifyAppUnresponsive(InputApplicationHandle applicationHandle, String reason) {
+ void notifyAppUnresponsive(InputApplicationHandle applicationHandle,
+ TimeoutRecord timeoutRecord) {
preDumpIfLockTooSlow();
final ActivityRecord activity;
synchronized (mService.mGlobalLock) {
@@ -70,31 +72,31 @@
+ ". Dropping notifyNoFocusedWindowAnr request");
return;
}
- Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: " + reason);
- dumpAnrStateLocked(activity, null /* windowState */, reason);
+ Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: " + timeoutRecord.mReason);
+ dumpAnrStateLocked(activity, null /* windowState */, timeoutRecord.mReason);
mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
}
- activity.inputDispatchingTimedOut(reason, INVALID_PID);
+ activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
}
/**
* Notify a window was unresponsive.
*
- * @param token - the input token of the window
- * @param pid - the pid of the window, if known
- * @param reason - the reason for the window being unresponsive
+ * @param token - the input token of the window
+ * @param pid - the pid of the window, if known
+ * @param timeoutRecord - details for the timeout
*/
void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
- @NonNull String reason) {
- if (notifyWindowUnresponsive(token, reason)) {
+ @NonNull TimeoutRecord timeoutRecord) {
+ if (notifyWindowUnresponsive(token, timeoutRecord)) {
return;
}
if (!pid.isPresent()) {
Slog.w(TAG_WM, "Failed to notify that window token=" + token + " was unresponsive.");
return;
}
- notifyWindowUnresponsive(pid.getAsInt(), reason);
+ notifyWindowUnresponsive(pid.getAsInt(), timeoutRecord);
}
/**
@@ -103,7 +105,8 @@
* @return true if the window was identified by the given input token and the request was
* handled, false otherwise.
*/
- private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken, String reason) {
+ private boolean notifyWindowUnresponsive(@NonNull IBinder inputToken,
+ TimeoutRecord timeoutRecord) {
preDumpIfLockTooSlow();
final int pid;
final boolean aboveSystem;
@@ -119,14 +122,14 @@
// embedded, then we will blame the pid instead.
activity = (windowState.mInputChannelToken == inputToken)
? windowState.mActivityRecord : null;
- Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
+ Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason);
aboveSystem = isWindowAboveSystem(windowState);
- dumpAnrStateLocked(activity, windowState, reason);
+ dumpAnrStateLocked(activity, windowState, timeoutRecord.mReason);
}
if (activity != null) {
- activity.inputDispatchingTimedOut(reason, pid);
+ activity.inputDispatchingTimedOut(timeoutRecord, pid);
} else {
- mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, reason);
+ mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, timeoutRecord);
}
return true;
}
@@ -134,15 +137,16 @@
/**
* Notify a window owned by the provided pid was unresponsive.
*/
- private void notifyWindowUnresponsive(int pid, String reason) {
+ private void notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord) {
+ Slog.i(TAG_WM,
+ "ANR in input window owned by pid=" + pid + ". Reason: " + timeoutRecord.mReason);
synchronized (mService.mGlobalLock) {
- Slog.i(TAG_WM, "ANR in input window owned by pid=" + pid + ". Reason: " + reason);
- dumpAnrStateLocked(null /* activity */, null /* windowState */, reason);
+ dumpAnrStateLocked(null /* activity */, null /* windowState */, timeoutRecord.mReason);
}
// We cannot determine the z-order of the window, so place the anr dialog as high
// as possible.
- mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, reason);
+ mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, timeoutRecord);
}
/**
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 5d2d582..acbf1a4 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
@@ -26,6 +27,7 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.media.projection.MediaProjectionManager;
import android.os.IBinder;
import android.provider.DeviceConfig;
import android.view.ContentRecordingSession;
@@ -38,7 +40,7 @@
/**
* Manages content recording for a particular {@link DisplayContent}.
*/
-final class ContentRecorder {
+final class ContentRecorder implements WindowContainerListener {
/**
* The key for accessing the device config that controls if task recording is supported.
@@ -51,6 +53,8 @@
@NonNull
private final DisplayContent mDisplayContent;
+ @Nullable private final MediaProjectionManagerWrapper mMediaProjectionManager;
+
/**
* The session for content recording, or null if this DisplayContent is not being used for
* recording.
@@ -73,8 +77,26 @@
*/
@Nullable private Rect mLastRecordedBounds = null;
+ /**
+ * The last configuration orientation.
+ */
+ private int mLastOrientation = ORIENTATION_UNDEFINED;
+
ContentRecorder(@NonNull DisplayContent displayContent) {
+ this(displayContent, () -> {
+ MediaProjectionManager mpm = displayContent.mWmService.mContext.getSystemService(
+ MediaProjectionManager.class);
+ if (mpm != null) {
+ mpm.stopActiveProjection();
+ }
+ });
+ }
+
+ @VisibleForTesting
+ ContentRecorder(@NonNull DisplayContent displayContent,
+ @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
mDisplayContent = displayContent;
+ mMediaProjectionManager = mediaProjectionManager;
}
/**
@@ -87,6 +109,10 @@
mContentRecordingSession = session;
}
+ boolean isContentRecordingSessionSet() {
+ return mContentRecordingSession != null;
+ }
+
/**
* Returns {@code true} if this DisplayContent is currently recording.
*/
@@ -95,7 +121,7 @@
}
/**
- * Start recording if this DisplayContent no longer has content. Stop recording if it now
+ * Start recording if this DisplayContent no longer has content. Pause recording if it now
* has content or the display is not on.
*/
@VisibleForTesting void updateRecording() {
@@ -187,7 +213,7 @@
/**
* Stops recording on this DisplayContent, and updates the session details.
*/
- void remove() {
+ void stopRecording() {
if (mRecordedSurface != null) {
// Do not wait for the mirrored surface to be garbage collected, but clean up
// immediately.
@@ -195,7 +221,20 @@
mRecordedSurface = null;
clearContentRecordingSession();
// Do not need to force remove the VirtualDisplay; this is handled by the media
- // projection service.
+ // projection service when the display is removed.
+ }
+ }
+
+
+ /**
+ * Ensure recording does not fall back to the display stack; ensure the recording is stopped
+ * and the client notified by tearing down the virtual display.
+ */
+ void stopMediaProjection() {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
+ if (mMediaProjectionManager != null) {
+ mMediaProjectionManager.stopActiveProjection();
}
}
@@ -299,8 +338,7 @@
mDisplayContent.mWmService.mWindowContextListenerController.getContainer(
tokenToRecord);
if (wc == null) {
- // Un-set the window token to record for this VirtualDisplay. Fall back to
- // Display stack capture for the entire display.
+ // Fall back to screenrecording using the data sent to DisplayManager
mDisplayContent.mWmService.mDisplayManagerInternal.setWindowManagerMirroring(
mDisplayContent.getDisplayId(), false);
handleStartRecordingFailed();
@@ -326,6 +364,8 @@
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Unable to retrieve task to start recording for "
+ "display %d", mDisplayContent.getDisplayId());
+ } else {
+ taskToRecord.registerWindowContainerListener(this);
}
return taskToRecord;
default:
@@ -342,9 +382,9 @@
/**
* Exit this recording session.
* <p>
- * If this is a task session, tear down the recording entirely. Do not fall back
- * to recording the entire display on the display stack; this would surprise the user
- * given they selected task capture.
+ * If this is a task session, stop the recording entirely, including the MediaProjection.
+ * Do not fall back to recording the entire display on the display stack; this would surprise
+ * the user given they selected task capture.
* </p><p>
* If this is a display session, just stop recording by layer mirroring. Fall back to recording
* from the display stack.
@@ -353,26 +393,15 @@
private void handleStartRecordingFailed() {
final boolean shouldExitTaskRecording = mContentRecordingSession != null
&& mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+ clearContentRecordingSession();
if (shouldExitTaskRecording) {
- // Clean up the cached session first, since tearing down the display will generate
- // display
- // events which will trickle back to here.
- clearContentRecordingSession();
- tearDownVirtualDisplay();
- } else {
- clearContentRecordingSession();
+ // Clean up the cached session first to ensure recording doesn't re-start, since
+ // tearing down the display will generate display events which will trickle back here.
+ stopMediaProjection();
}
}
/**
- * Ensure recording does not fall back to the display stack; ensure the recording is stopped
- * and the client notified by tearing down the virtual display.
- */
- private void tearDownVirtualDisplay() {
- // TODO(b/219761722) Clean up the VirtualDisplay if task mirroring fails
- }
-
- /**
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
* fit and centred in the output surface.
*
@@ -442,4 +471,37 @@
}
return surfaceSize;
}
+
+ // WindowContainerListener
+ @Override
+ public void onRemoved() {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Recorded task is removed, so stop recording on display %d",
+ mDisplayContent.getDisplayId());
+ Task recordedTask = mRecordedWindowContainer.asTask();
+ if (recordedTask == null
+ || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+ return;
+ }
+ recordedTask.unregisterWindowContainerListener(this);
+ // Stop mirroring and teardown.
+ clearContentRecordingSession();
+ // Clean up the cached session first to ensure recording doesn't re-start, since
+ // tearing down the display will generate display events which will trickle back here.
+ stopMediaProjection();
+ }
+
+ // WindowContainerListener
+ @Override
+ public void onMergedOverrideConfigurationChanged(
+ Configuration mergedOverrideConfiguration) {
+ WindowContainerListener.super.onMergedOverrideConfigurationChanged(
+ mergedOverrideConfiguration);
+ onConfigurationChanged(mLastOrientation);
+ mLastOrientation = mergedOverrideConfiguration.orientation;
+ }
+
+ @VisibleForTesting interface MediaProjectionManagerWrapper {
+ void stopActiveProjection();
+ }
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index fff7637..1efc202 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -56,14 +56,13 @@
* Updates the current recording session. If a new display is taking over recording, then
* stops the prior display from recording.
*
- * @param incomingSession the new recording session. Should either be {@code null}, to stop
- * the current session, or a session on a new/different display than the
- * current session.
+ * @param incomingSession the new recording session. Should either have a {@code null} token, to
+ * stop the current session, or a session on a new/different display
+ * than the current session.
* @param wmService the window manager service
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
- // TODO(b/219761722) handle a null session arriving due to task setup failing
if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
|| ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
// Ignore an invalid session, or a session for the same display as currently recording.
@@ -82,8 +81,7 @@
}
if (mSession != null) {
// Update the pre-existing display about the new session.
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Pause the recording session on display %s",
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Pause the recording session on display %s",
mDisplayContent.getDisplayId());
mDisplayContent.pauseRecording();
mDisplayContent.setContentRecordingSession(null);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b54cd41..fccf54d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4416,13 +4416,20 @@
*/
@VisibleForTesting
InsetsControlTarget computeImeControlTarget() {
+ if (mImeInputTarget == null) {
+ // A special case that if there is no IME input target while the IME is being killed,
+ // in case seeing unexpected IME surface visibility change when delivering the IME leash
+ // to the remote insets target during the IME restarting, but the focus window is not in
+ // multi-windowing mode, return null target until the next input target updated.
+ return null;
+ }
+
+ final WindowState imeInputTarget = mImeInputTarget.getWindowState();
if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
- || (mImeInputTarget != null
- && getImeHostOrFallback(mImeInputTarget.getWindowState())
- == mRemoteInsetsControlTarget)) {
+ || getImeHostOrFallback(imeInputTarget) == mRemoteInsetsControlTarget) {
return mRemoteInsetsControlTarget;
} else {
- return mImeInputTarget != null ? mImeInputTarget.getWindowState() : null;
+ return imeInputTarget;
}
}
@@ -6075,7 +6082,7 @@
mRemoved = true;
if (mContentRecorder != null) {
- mContentRecorder.remove();
+ mContentRecorder.stopRecording();
}
// Only update focus/visibility for the last one because there may be many root tasks are
@@ -6356,6 +6363,15 @@
}
/**
+ * The MediaProjection instance is torn down.
+ */
+ @VisibleForTesting void stopMediaProjection() {
+ if (mContentRecorder != null) {
+ mContentRecorder.stopMediaProjection();
+ }
+ }
+
+ /**
* Sets the incoming recording session. Should only be used when starting to record on
* this display; stopping recording is handled separately when the display is destroyed.
*
@@ -6366,11 +6382,73 @@
}
/**
+ * This is to enable mirroring on virtual displays that specify the
+ * {@link android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} but don't
+ * mirror using MediaProjection. When done through MediaProjection API, the
+ * ContentRecordingSession will be created automatically.
+ *
+ * This should only be called when there's no ContentRecordingSession already set for this
+ * display. The code will ask DMS if this display should enable display mirroring and which
+ * displayId to mirror from.
+ *
+ * @return true if the {@link ContentRecordingSession} was set for display mirroring using data
+ * from DMS, false if there was no ContentRecordingSession created.
+ */
+ boolean setDisplayMirroring() {
+ int mirrorDisplayId = mWmService.mDisplayManagerInternal.getDisplayIdToMirror(mDisplayId);
+ if (mirrorDisplayId == INVALID_DISPLAY) {
+ return false;
+ }
+
+ if (mirrorDisplayId == mDisplayId) {
+ if (mDisplayId != DEFAULT_DISPLAY) {
+ ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
+ "Attempting to mirror self on %d", mirrorDisplayId);
+ }
+ return false;
+ }
+
+ // This is very unlikely, and probably impossible, but if the current display is
+ // DEFAULT_DISPLAY and the displayId to mirror results in an invalid display, we don't want
+ // to mirror the DEFAULT_DISPLAY so instead we just return
+ DisplayContent mirrorDc = mRootWindowContainer.getDisplayContentOrCreate(mirrorDisplayId);
+ if (mirrorDc == null && mDisplayId == DEFAULT_DISPLAY) {
+ ProtoLog.w(WM_DEBUG_CONTENT_RECORDING, "Found no matching mirror display for id=%d for"
+ + " DEFAULT_DISPLAY. Nothing to mirror.", mirrorDisplayId);
+ return false;
+ }
+
+ if (mirrorDc == null) {
+ mirrorDc = mRootWindowContainer.getDefaultDisplay();
+ ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
+ "Attempting to mirror %d from %d but no DisplayContent associated. Changing "
+ + "to mirror default display.",
+ mirrorDisplayId, mDisplayId);
+ }
+
+ ContentRecordingSession session = ContentRecordingSession
+ .createDisplaySession(mirrorDc.getDisplayUiContext().getWindowContextToken())
+ .setDisplayId(mDisplayId);
+ setContentRecordingSession(session);
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Successfully created a ContentRecordingSession for displayId=%d to mirror "
+ + "content from displayId=%d",
+ mDisplayId, mirrorDisplayId);
+ return true;
+ }
+
+ /**
* Start recording if this DisplayContent no longer has content. Stop recording if it now
* has content or the display is not on.
*/
@VisibleForTesting void updateRecording() {
- getContentRecorder().updateRecording();
+ if (mContentRecorder == null || !mContentRecorder.isContentRecordingSessionSet()) {
+ if (!setDisplayMirroring()) {
+ return;
+ }
+ }
+
+ mContentRecorder.updateRecording();
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 25d187f..0769406 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1725,16 +1725,6 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
- // Check if current activity is letterboxed in order create a LetterboxDetails
- // component to be passed to SysUI for status bar treatment
- final ActivityRecord currentActivity = win.getActivityRecord();
- if (currentActivity != null) {
- final LetterboxDetails currentLetterboxDetails = currentActivity
- .mLetterboxUiController.getLetterboxDetails();
- if (currentLetterboxDetails != null) {
- mLetterboxDetails.add(currentLetterboxDetails);
- }
- }
}
}
@@ -1752,6 +1742,17 @@
mNavBarBackgroundWindow = win;
}
}
+
+ // Check if current activity is letterboxed in order create a LetterboxDetails
+ // component to be passed to SysUI for status bar treatment
+ final ActivityRecord currentActivity = win.getActivityRecord();
+ if (currentActivity != null) {
+ final LetterboxDetails currentLetterboxDetails = currentActivity
+ .mLetterboxUiController.getLetterboxDetails();
+ if (currentLetterboxDetails != null) {
+ mLetterboxDetails.add(currentLetterboxDetails);
+ }
+ }
} else if (win.isDimming()) {
if (mStatusBar != null) {
addStatusBarAppearanceRegionsForDimmingWindow(
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 33cdd2e..1e9d451 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -36,6 +36,7 @@
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
+import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.input.InputManagerService;
@@ -95,14 +96,17 @@
*/
@Override
public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
- mService.mAnrController.notifyAppUnresponsive(
- applicationHandle, "Application does not have a focused window");
+ TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
+ timeoutMessage("Application does not have a focused window"));
+ mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
}
@Override
public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
- @NonNull String reason) {
- mService.mAnrController.notifyWindowUnresponsive(token, pid, reason);
+ String reason) {
+ TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+ timeoutMessage(reason));
+ mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
}
@Override
@@ -341,6 +345,13 @@
mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
}
+ private String timeoutMessage(String reason) {
+ if (reason == null) {
+ return "Input dispatching timed out";
+ }
+ return "Input dispatching timed out (" + reason + ")";
+ }
+
void dump(PrintWriter pw, String prefix) {
if (mInputFreezeReason != null) {
pw.println(prefix + "mInputFreezeReason=" + mInputFreezeReason);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
index a618f7c..a0e22e7 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsUtil.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -63,7 +63,7 @@
* Calculate the default size for a freeform environment. |defaultSize| is used as the default
* DP size, but if this is null, the portrait phone size is used.
*/
- static Size getDefaultFreeformSize(@NonNull ActivityInfo info,
+ static Size getDefaultFreeformSize(@NonNull ActivityRecord activityRecord,
@NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int orientation,
@NonNull Rect stableBounds) {
@@ -98,8 +98,8 @@
final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
// Aspect ratio requirements.
- final float minAspectRatio = info.getMinAspectRatio(orientation);
- final float maxAspectRatio = info.getMaxAspectRatio();
+ final float minAspectRatio = activityRecord.getMinAspectRatio();
+ final float maxAspectRatio = activityRecord.info.getMaxAspectRatio();
// Adjust the width and height to the aspect ratio requirements.
int adjWidth = width;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ec9ee29..c8ed602 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -257,6 +257,10 @@
: mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
}
+ return getSplitScreenAspectRatio();
+ }
+
+ float getSplitScreenAspectRatio() {
int dividerWindowWidth =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
int dividerInsets =
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8de556a..0332935 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -489,6 +489,7 @@
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
private int mForceHiddenFlags = 0;
+ private boolean mForceTranslucent = false;
// TODO(b/160201781): Revisit double invocation issue in Task#removeChild.
/**
@@ -4342,6 +4343,10 @@
return true;
}
+ void setForceTranslucent(boolean set) {
+ mForceTranslucent = set;
+ }
+
@Override
public boolean isAlwaysOnTop() {
return !isForceHidden() && super.isAlwaysOnTop();
@@ -4360,6 +4365,11 @@
}
@Override
+ protected boolean isForceTranslucent() {
+ return mForceTranslucent;
+ }
+
+ @Override
long getProtoFieldId() {
return TASK;
}
@@ -5333,7 +5343,7 @@
abort = true;
}
if (abort) {
- Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent);
+ android.util.EventLog.writeEvent(0x534e4554, "238605611", callingUid, "");
foundParentInTask = false;
} else {
parent.deliverNewIntentLocked(callingUid, destIntent, destGrants,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e6afdd6..c1f06e4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -740,6 +740,10 @@
return false;
}
+ protected boolean isForceTranslucent() {
+ return false;
+ }
+
boolean isLeafTaskFragment() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asTaskFragment() != null) {
@@ -865,7 +869,7 @@
*/
@VisibleForTesting
boolean isTranslucent(ActivityRecord starting) {
- if (!isAttached() || isForceHidden()) {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 7b0337d..1362094 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -746,7 +746,7 @@
// First we get the default size we want.
displayArea.getStableRect(mTmpStableBounds);
- final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root.info, displayArea,
+ final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root, displayArea,
layout, orientation, mTmpStableBounds);
mTmpBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6121ad4..da5f5d0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -882,6 +882,8 @@
* Must be invoked for a valid MediaProjection session.
*
* @param incomingSession the nullable incoming content recording session
+ * @return {@code true} if successfully set the session, or {@code false} if the session
+ * could not be prepared and the session needs to be torn down.
*/
- public abstract void setContentRecordingSession(ContentRecordingSession incomingSession);
+ public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 79037ab..fd54f78 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -899,6 +899,11 @@
mMaximumObscuringOpacityForTouch = Settings.Global.getFloat(resolver,
Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
+ if (mMaximumObscuringOpacityForTouch < 0.0f
+ || mMaximumObscuringOpacityForTouch > 1.0f) {
+ mMaximumObscuringOpacityForTouch =
+ InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
+ }
}
void updateSystemUiSettings(boolean handleChange) {
@@ -8286,14 +8291,15 @@
}
@Override
- public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
+ public boolean setContentRecordingSession(
+ @Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
- // Allow the controller to handle teardown or a non-task session.
+ // Allow the controller to handle teardown of a non-task session.
if (incomingSession == null
|| incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
- return;
+ return true;
}
// For a task session, find the activity identified by the launch cookie.
final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
@@ -8301,15 +8307,14 @@
if (wct == null) {
Slog.w(TAG, "Handling a new recording session; unable to find the "
+ "WindowContainerToken");
- mContentRecordingController.setContentRecordingSessionLocked(null,
- WindowManagerService.this);
- return;
+ return false;
}
// Replace the launch cookie in the session details with the task's
// WindowContainerToken.
incomingSession.setTokenToRecord(wct.asBinder());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
+ return true;
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 95db746..3b9cd36 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -646,6 +646,12 @@
}
}
+ if ((c.getChangeMask()
+ & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) {
+ tr.setForceTranslucent(c.getForceTranslucent());
+ effects = TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
tr.setActivityWindowingMode(childWindowingMode);
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ca834c5..efcca5d 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -22,6 +22,7 @@
import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
@@ -40,6 +41,7 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
/**
* A class that allows window manager to dump its state continuously to a trace file, such that a
@@ -352,6 +354,10 @@
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
mBuffer.writeTraceToFile(mTraceFile, proto);
} catch (IOException e) {
Log.e(TAG, "Unable to write buffer to file", e);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 09044e7..073b131c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -45,6 +45,8 @@
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
+ <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
+ maxOccurs="1" />
<xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
<xs:annotation name="final"/>
</xs:element>
@@ -101,6 +103,21 @@
<!-- Type definitions -->
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="displayQuirks">
<xs:sequence>
<xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
@@ -341,5 +358,4 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
-
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e8b13ca..e9a9269 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,6 +1,14 @@
// Signature format: 2.0
package com.android.server.display.config {
+ public class AutoBrightness {
+ ctor public AutoBrightness();
+ method public final java.math.BigInteger getBrighteningLightDebounceMillis();
+ method public final java.math.BigInteger getDarkeningLightDebounceMillis();
+ method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
+ method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method @NonNull public final java.math.BigDecimal getMinimum();
@@ -40,6 +48,7 @@
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
+ method public com.android.server.display.config.AutoBrightness getAutoBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -58,6 +67,7 @@
method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
+ method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index 2219d47..e2f56ba 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -301,6 +301,35 @@
verify(mUserOneService, never()).initializeTransports(transports, /* observer */ null);
}
+ /**
+ * Test that the backup services throws a {@link SecurityException} if the caller does not have
+ * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
+ */
+ @Test
+ public void testIsUserReadyForBackup_withoutPermission_throwsSecurityException() {
+ BackupManagerService backupManagerService = createService();
+ registerUser(backupManagerService, mUserOneId, mUserOneService);
+ setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
+
+ expectThrows(
+ SecurityException.class,
+ () -> backupManagerService.isUserReadyForBackup(mUserOneId));
+ }
+
+ /**
+ * Test that the backup service does not throw a {@link SecurityException} if the caller has
+ * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
+ */
+ @Test
+ public void testIsUserReadyForBackup_withPermission_callsMethodForUser() {
+ BackupManagerService backupManagerService = createService();
+ registerUser(backupManagerService, UserHandle.USER_SYSTEM, mUserSystemService);
+ registerUser(backupManagerService, mUserOneId, mUserOneService);
+ setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true);
+
+ assertThat(backupManagerService.isUserReadyForBackup(mUserOneId)).isTrue();
+ }
+
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception {
diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
index 8a9845b..b25f265 100644
--- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static com.android.server.backup.testing.TransportData.genericTransport;
@@ -326,7 +327,25 @@
.setApplicationEnabledSetting(
PACKAGE_A,
Integer.valueOf(COMPONENT_ENABLED_STATE_DISABLED),
- 0 /*flags*/);
+ /* flags */ 0);
+ transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportB1));
+ verify(mListener, never()).onTransportRegistered(any(), any());
+ }
+
+ @Test
+ public void testOnPackageChanged_whenPackageChanged_packageDisabledByUserUnregistersTransport()
+ throws Exception {
+ TransportManager transportManager =
+ createTransportManagerWithRegisteredTransports(mTransportA1, mTransportB1);
+ reset(mListener);
+
+ mContext.getPackageManager()
+ .setApplicationEnabledSetting(
+ PACKAGE_A,
+ Integer.valueOf(COMPONENT_ENABLED_STATE_DISABLED_USER),
+ /* flags */ 0);
transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
assertRegisteredTransports(transportManager, singletonList(mTransportB1));
@@ -344,7 +363,7 @@
.setApplicationEnabledSetting(
PACKAGE_A,
Integer.valueOf(COMPONENT_ENABLED_STATE_DISABLED),
- 0 /*flags*/);
+ /* flags */ 0);
transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
assertRegisteredTransports(transportManager, singletonList(mTransportB1));
@@ -354,7 +373,7 @@
.setApplicationEnabledSetting(
PACKAGE_A,
Integer.valueOf(COMPONENT_ENABLED_STATE_ENABLED),
- 0 /*flags*/);
+ /* flags */ 0);
transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportB1));
@@ -363,7 +382,7 @@
}
@Test
- public void testOnPackageChanged_whenPackageChanged_unknownComponentStateIsIgnored()
+ public void testOnPackageChanged_whenPackageChanged_packageDefaultEnabledRegistersTransport()
throws Exception {
TransportManager transportManager =
createTransportManagerWithRegisteredTransports(mTransportA1, mTransportB1);
@@ -372,12 +391,23 @@
mContext.getPackageManager()
.setApplicationEnabledSetting(
PACKAGE_A,
+ Integer.valueOf(COMPONENT_ENABLED_STATE_DISABLED_USER),
+ /* flags */ 0);
+ transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportB1));
+ verify(mListener, never()).onTransportRegistered(any(), any());
+
+ mContext.getPackageManager()
+ .setApplicationEnabledSetting(
+ PACKAGE_A,
Integer.valueOf(COMPONENT_ENABLED_STATE_DEFAULT),
- 0 /*flags*/);
+ /* flags */ 0);
transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A);
assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportB1));
- verify(mListener, never()).onTransportRegistered(any(), any());
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 57a8013..427c3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -100,7 +100,9 @@
// Capture the listeners.
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+
+ mFlexibilityController =
+ new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class));
mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
verify(mContext).registerReceiver(receiverCaptor.capture(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 7a70e7a..953a72d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -116,8 +116,8 @@
LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
-
- mFlexibilityController = new FlexibilityController(mService);
+ mFlexibilityController =
+ new FlexibilityController(mService, mock(PrefetchController.class));
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6d9f48d..35e6db9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -16,11 +16,15 @@
package com.android.server.job.controllers;
+import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE;
+import static android.app.job.JobInfo.BIAS_TOP_APP;
import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.job.controllers.FlexibilityController.DEFAULT_FLEXIBILITY_DEADLINE;
import static com.android.server.job.controllers.FlexibilityController.FcConstants.KEY_FLEXIBILITY_ENABLED;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -67,7 +71,7 @@
public class FlexibilityControllerTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
-
+ private static final long FROZEN_TIME = 100L;
private MockitoSession mMockingSession;
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
@@ -80,6 +84,8 @@
private Context mContext;
@Mock
private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PrefetchController mPrefetchController;
@Before
public void setup() {
@@ -117,11 +123,12 @@
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Freeze the clocks at a moment in time
JobSchedulerService.sSystemClock =
- Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
// Initialize real objects.
- mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+ mFlexibilityController = new FlexibilityController(mJobSchedulerService,
+ mPrefetchController);
mFcConstants = mFlexibilityController.getFcConstants();
setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
@@ -149,76 +156,202 @@
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
JobInfo jobInfo = job.build();
- return JobStatus.createFromJobInfo(
+ JobStatus js = JobStatus.createFromJobInfo(
jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ js.enqueueTime = FROZEN_TIME;
+ return js;
}
@Test
- public void testGetNextConstraintDropTimeElapsed() {
+ public void testGetNextConstraintDropTimeElapsedLocked() {
long nextTimeToDropNumConstraints;
// no delay, deadline
JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
JobStatus js = createJobStatus("time", jb);
- js.enqueueTime = 100L;
- assertEquals(0, js.getEarliestRunTime());
- assertEquals(1100L, js.getLatestRunTimeElapsed());
- assertEquals(100L, js.enqueueTime);
+ assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
+ assertEquals(1000 + FROZEN_TIME, js.getLatestRunTimeElapsed());
+ assertEquals(FROZEN_TIME, js.enqueueTime);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(600L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(700L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(800L, nextTimeToDropNumConstraints);
// delay, no deadline
jb = createJob(0).setMinimumLatency(800000L);
js = createJobStatus("time", jb);
- js.enqueueTime = 100L;
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(130400100, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(156320100L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(182240100L, nextTimeToDropNumConstraints);
// no delay, no deadline
jb = createJob(0);
js = createJobStatus("time", jb);
- js.enqueueTime = 100L;
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(129600100, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(155520100L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(181440100L, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
js = createJobStatus("time", jb);
- js.enqueueTime = 100L;
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(700L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(800L, nextTimeToDropNumConstraints);
js.adjustNumRequiredFlexibleConstraints(-1);
- nextTimeToDropNumConstraints = mFlexibilityController.getNextConstraintDropTimeElapsed(js);
+ nextTimeToDropNumConstraints = mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js);
assertEquals(900L, nextTimeToDropNumConstraints);
}
@Test
+ public void testCurPercent() {
+ long deadline = 1000;
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
+ JobStatus js = createJobStatus("time", jb);
+
+ assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ assertEquals(deadline + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME), ZoneOffset.UTC);
+ assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
+ assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME), ZoneOffset.UTC);
+ assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+ long delay = 100;
+ deadline = 1100;
+ jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
+ js = createJobStatus("time", jb);
+
+ assertEquals(FROZEN_TIME + delay,
+ mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ assertEquals(deadline + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME + delay), ZoneOffset.UTC);
+
+ assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
+ assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME + delay), ZoneOffset.UTC);
+ assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ }
+
+ @Test
+ public void testGetLifeCycleBeginningElapsedLocked_prefetch() {
+ // prefetch with lifecycle
+ when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
+ JobInfo.Builder jb = createJob(0).setPrefetch(true);
+ JobStatus js = createJobStatus("time", jb);
+ when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
+ assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ // prefetch with enqueue
+ jb = createJob(0).setPrefetch(true);
+ js = createJobStatus("time", jb);
+ assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ // prefetch with delay
+ jb = createJob(0).setPrefetch(true).setMinimumLatency(200);
+ js = createJobStatus("time", jb);
+ assertEquals(200 + FROZEN_TIME, js.getEarliestRunTime());
+ assertEquals(js.getEarliestRunTime(),
+ mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ // prefetch without estimate
+ mFlexibilityController.mPrefetchLifeCycleStart
+ .add(js.getUserId(), js.getSourcePackageName(), 500L);
+ when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+ jb = createJob(0).setPrefetch(true);
+ js = createJobStatus("time", jb);
+ assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ }
+
+ @Test
+ public void testGetLifeCycleBeginningElapsedLocked_nonPrefetch() {
+ // delay
+ long delay = 100;
+ JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
+ JobStatus js = createJobStatus("time", jb);
+ assertEquals(delay + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ // no delay
+ jb = createJob(0);
+ js = createJobStatus("time", jb);
+ assertEquals(FROZEN_TIME,
+ mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ }
+
+ @Test
+ public void testGetLifeCycleEndElapsedLocked_prefetch() {
+ // prefetch no estimate
+ JobInfo.Builder jb = createJob(0).setPrefetch(true);
+ JobStatus js = createJobStatus("time", jb);
+ when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
+ assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ // prefetch with estimate
+ jb = createJob(0).setPrefetch(true);
+ js = createJobStatus("time", jb);
+ when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
+ assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ }
+ @Test
+ public void testGetLifeCycleEndElapsedLocked_nonPrefetch() {
+ // deadline
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
+ JobStatus js = createJobStatus("time", jb);
+ assertEquals(1000L + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ // no deadline
+ jb = createJob(0);
+ js = createJobStatus("time", jb);
+ assertEquals(100L + DEFAULT_FLEXIBILITY_DEADLINE,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+ }
+
+ @Test
public void testWontStopJobFromRunning() {
JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
// Stop satisfied constraints from causing a false positive.
@@ -233,8 +366,9 @@
public void testFlexibilityTracker() {
FlexibilityController.FlexibilityTracker flexTracker =
mFlexibilityController.new
- FlexibilityTracker(FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
+ FlexibilityTracker(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS);
+ assertEquals(4, flexTracker.size());
JobStatus[] jobs = new JobStatus[4];
JobInfo.Builder jb;
for (int i = 0; i < jobs.length; i++) {
@@ -282,6 +416,29 @@
assertEquals(0, trackedJobs.get(1).size());
assertEquals(1, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
+
+ flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+ assertEquals(0, trackedJobs.get(0).size());
+ assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(2, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
+
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -2);
+
+ assertEquals(1, trackedJobs.get(0).size());
+ assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(
+ (DEFAULT_FLEXIBILITY_DEADLINE / 2) + HOUR_IN_MILLIS), ZoneOffset.UTC);
+
+ flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+ assertEquals(0, trackedJobs.get(0).size());
+ assertEquals(1, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
}
}
@@ -303,14 +460,6 @@
}
@Test
- public void testExceptions_Prefetch() {
- JobInfo.Builder jb = createJob(0);
- jb.setPrefetch(true);
- JobStatus js = createJobStatus("testExceptions_Prefetch", jb);
- assertFalse(js.hasFlexibilityConstraint());
- }
-
- @Test
public void testExceptions_NoFlexibleConstraints() {
JobInfo.Builder jb = createJob(0);
jb.setRequiresDeviceIdle(true);
@@ -381,7 +530,7 @@
@Test
public void testSetConstraintSatisfied_Jobs() {
JobInfo.Builder jb;
- int[] constraintPermutations = {
+ int[] constraintCombinations = {
CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
@@ -393,9 +542,9 @@
};
int constraints;
- for (int i = 0; i < constraintPermutations.length; i++) {
+ for (int i = 0; i < constraintCombinations.length; i++) {
jb = createJob(i);
- constraints = constraintPermutations[i];
+ constraints = constraintCombinations[i];
jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
@@ -410,8 +559,8 @@
assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
- for (int i = 0; i < constraintPermutations.length; i++) {
- constraints = constraintPermutations[i];
+ for (int i = 0; i < constraintCombinations.length; i++) {
+ constraints = constraintCombinations[i];
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
(constraints & CONSTRAINT_CHARGING) != 0);
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
@@ -427,6 +576,73 @@
}
}
+ @Test
+ public void testOnPrefetchCacheUpdated() {
+ ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
+ JobInfo.Builder jb = createJob(22).setPrefetch(true);
+ JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
+ jobs.add(js);
+ when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
+
+ mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+ mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
+
+ when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
+ 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC);
+
+ mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
+ jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
+ 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+
+ assertEquals(150L,
+ (long) mFlexibilityController.mPrefetchLifeCycleStart
+ .get(js.getSourceUserId(), js.getSourcePackageName()));
+ assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
+ assertEquals(1150L,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+ assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ assertEquals(0, js.getNumDroppedFlexibleConstraints());
+ assertEquals(650L, mFlexibilityController
+ .getNextConstraintDropTimeElapsedLocked(js));
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ }
+
+ /**
+ * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
+ * the estimated launch time was updated and the last time the app was opened.
+ * When the UID bias updates it means the app might have been opened.
+ * This tests that the cached value is updated properly.
+ */
+ @Test
+ public void testUidUpdatesLifeCycle() {
+ JobInfo.Builder jb = createJob(0).setPrefetch(true);
+ JobStatus js = createJobStatus("uidTest", jb);
+ mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+ mJobStore.add(js);
+
+ final ArraySet<String> pkgs = new ArraySet<>();
+ pkgs.add(js.getSourcePackageName());
+ when(mJobSchedulerService.getPackagesForUidLocked(js.getUid())).thenReturn(pkgs);
+
+ setUidBias(js.getUid(), BIAS_TOP_APP);
+ setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+ assertEquals(100L, (long) mFlexibilityController.mPrefetchLifeCycleStart
+ .getOrDefault(js.getSourceUserId(), js.getSourcePackageName(), 0L));
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(50L), ZoneOffset.UTC);
+
+ setUidBias(js.getUid(), BIAS_TOP_APP);
+ setUidBias(js.getUid(), BIAS_FOREGROUND_SERVICE);
+ assertEquals(100L, (long) mFlexibilityController
+ .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
+
+ }
+
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index bcdfc35..7242b1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -480,4 +480,32 @@
sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
}
+
+ @Test
+ public void testRegisterOnPrefetchChangedListener() {
+ when(mUsageStatsManagerInternal
+ .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
+ .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
+ // Needs to get wrapped in an array to get accessed by an inner class.
+ final boolean[] onPrefetchCacheChangedCalled = new boolean[1];
+ final PrefetchController.PrefetchChangedListener prefetchChangedListener =
+ new PrefetchController.PrefetchChangedListener() {
+ @Override
+ public void onPrefetchCacheUpdated(
+ ArraySet<JobStatus> jobs, int userId, String pkgName,
+ long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+ onPrefetchCacheChangedCalled[0] = true;
+ }
+ };
+ mPrefetchController.registerPrefetchChangedListener(prefetchChangedListener);
+
+ JobStatus jobStatus = createJobStatus("testRegisterOnPrefetchChangedListener", 1);
+ trackJobs(jobStatus);
+
+ mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
+ SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
+ verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
+
+ assertTrue(onPrefetchCacheChangedCalled[0]);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 842b23c..4a16874 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -17,13 +17,13 @@
package com.android.server.accessibility;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
import static org.junit.Assert.assertEquals;
@@ -528,7 +528,8 @@
// different client that holds different fetch flags for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
mMockClient2InteractionId,
- FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ FLAG_PREFETCH_SIBLINGS
+ | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
| FLAG_PREFETCH_ANCESTORS);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index c940ef5..0b84a60 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -37,6 +37,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.os.TimeoutRecord;
import com.android.server.appop.AppOpsService;
import com.android.server.wm.WindowProcessController;
@@ -111,12 +112,15 @@
final WindowProcessController parentProcess = mock(WindowProcessController.class);
final boolean aboveSystem = false;
final String annotation = "test";
+ final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+ annotation);
mAnrHelper.appNotResponding(mAnrApp, activityShortComponentName, appInfo,
- parentShortComponentName, parentProcess, aboveSystem, annotation);
+ parentShortComponentName, parentProcess, aboveSystem, timeoutRecord);
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
- eq(parentProcess), eq(aboveSystem), eq(annotation), eq(false) /* onlyDumpSelf */);
+ eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+ eq(false) /* onlyDumpSelf */);
}
@Test
@@ -129,11 +133,13 @@
processingLatch.await();
return null;
}).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
- anyBoolean(), anyString(), anyBoolean());
+ anyBoolean(), any(), anyBoolean());
final ApplicationInfo appInfo = new ApplicationInfo();
+ final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
+ "annotation");
final Runnable reportAnr = () -> mAnrHelper.appNotResponding(mAnrApp,
"activityShortComponentName", appInfo, "parentShortComponentName",
- null /* parentProcess */, false /* aboveSystem */, "annotation");
+ null /* parentProcess */, false /* aboveSystem */, timeoutRecord);
reportAnr.run();
// This should be skipped because the pid is pending in queue.
reportAnr.run();
@@ -149,6 +155,6 @@
processingLatch.countDown();
// There is only one ANR reported.
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
- anyString(), any(), any(), any(), anyBoolean(), anyString(), anyBoolean());
+ anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 6538a17..70519e4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -34,6 +34,7 @@
import androidx.test.filters.FlakyTest;
+import com.android.internal.os.TimeoutRecord;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerService;
@@ -195,8 +196,9 @@
private static void appNotResponding(ProcessErrorStateRecord processErrorState,
String annotation) {
+ TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
- false /* aboveSystem */, annotation, false /* onlyDumpSelf */);
+ false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 7939303..03ea613 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -86,6 +86,8 @@
0.0f);
assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+ assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
+ assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
@@ -109,6 +111,10 @@
+ "<nits>800.0</nits>\n"
+ "</point>\n"
+ "</screenBrightnessMap>\n"
+ + "<autoBrightness>\n"
+ + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+ + "</autoBrightness>\n"
+ "<highBrightnessMode enabled=\"true\">\n"
+ "<transitionPoint>0.62</transitionPoint>\n"
+ "<minimumLux>10000</minimumLux>\n"
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index c11e2b0..3eb1dea 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
@@ -577,6 +578,7 @@
// This is effectively the DisplayManager service published to ServiceManager.
DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
final String uniqueId = "uniqueId --- displayIdToMirrorTest";
final int width = 600;
@@ -606,12 +608,58 @@
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
// The displayId to mirror should be a default display if there is none initially.
- assertEquals(displayManager.getDisplayIdToMirrorInternal(firstDisplayId),
+ assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
Display.DEFAULT_DISPLAY);
- assertEquals(displayManager.getDisplayIdToMirrorInternal(secondDisplayId),
+ assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
firstDisplayId);
}
+ @Test
+ public void testGetDisplayIdToMirror() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+
+ // This is effectively the DisplayManager service published to ServiceManager.
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
+
+ final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+ final int width = 600;
+ final int height = 800;
+ final int dpi = 320;
+
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi)
+ .setUniqueId(uniqueId)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ // The second virtual display requests to mirror the first virtual display.
+ final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+ when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+ final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi)
+ .setUniqueId(uniqueId2)
+ .setWindowManagerMirroring(true);
+ final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+ mMockAppToken2 /* callback */, null /* projection */,
+ PACKAGE_NAME);
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+ // flush the handler
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+ // The displayId to mirror should be a invalid since the display had flag OWN_CONTENT_ONLY
+ assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
+ Display.INVALID_DISPLAY);
+ // The second display has mirroring managed by WindowManager so the mirror displayId should
+ // be invalid.
+ assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
+ Display.INVALID_DISPLAY);
+ }
+
/**
* Tests that the virtual display is created with
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java
new file mode 100644
index 0000000..5e92983
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubClientBrokerTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.location.contexthub;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.IContextHubClientCallback;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ContextHubClientBrokerTest {
+ private static final short HOST_ENDPOINT_ID = 123;
+ private static final String ATTRIBUTE_TAG = "attribute_tag";
+ private static final long NANOAPP_ID = 3210L;
+ private ContextHubClientManager mClientManager;
+ private Context mContext;
+ @Mock private IContextHubWrapper mMockContextHubWrapper;
+ @Mock private ContextHubInfo mMockContextHubInfo;
+ @Mock private IContextHubClientCallback mMockCallback;
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Before
+ public void setUp() throws RemoteException {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mClientManager = new ContextHubClientManager(mContext, mMockContextHubWrapper);
+ when(mMockCallback.asBinder()).thenReturn(new Binder());
+ }
+
+ private ContextHubClientBroker createFromCallback() {
+ ContextHubClientBroker broker =
+ new ContextHubClientBroker(
+ mContext,
+ mMockContextHubWrapper,
+ mClientManager,
+ mMockContextHubInfo,
+ HOST_ENDPOINT_ID,
+ mMockCallback,
+ ATTRIBUTE_TAG,
+ new ContextHubTransactionManager(
+ mMockContextHubWrapper, mClientManager, new NanoAppStateManager()),
+ mContext.getPackageName());
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ return broker;
+ }
+
+ private ContextHubClientBroker createFromPendingIntent(PendingIntent pendingIntent) {
+ ContextHubClientBroker broker =
+ new ContextHubClientBroker(
+ mContext,
+ mMockContextHubWrapper,
+ mClientManager,
+ mMockContextHubInfo,
+ HOST_ENDPOINT_ID,
+ pendingIntent,
+ NANOAPP_ID,
+ ATTRIBUTE_TAG,
+ new ContextHubTransactionManager(
+ mMockContextHubWrapper, mClientManager, new NanoAppStateManager()));
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ return broker;
+ }
+
+ @Test
+ // TODO(b/241016627): We should have similar tests for other public callbacks too.
+ public void testWakeLock_callback_onNanoAppLoaded() {
+ ContextHubClientBroker broker = createFromCallback();
+
+ broker.onNanoAppLoaded(NANOAPP_ID);
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+ broker.callbackFinished();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_callback_multiple() {
+ ContextHubClientBroker broker = createFromCallback();
+
+ broker.onNanoAppLoaded(NANOAPP_ID);
+ broker.onNanoAppUnloaded(NANOAPP_ID);
+ broker.onHubReset();
+
+ broker.callbackFinished();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+ broker.callbackFinished();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isTrue();
+
+ broker.callbackFinished();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_callback_binderDied() {
+ ContextHubClientBroker broker = createFromCallback();
+
+ broker.binderDied();
+
+ assertThat(broker.isWakelockUsable()).isFalse();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_pendingIntent() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+ ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+ CountDownLatch latch = new CountDownLatch(1);
+ PendingIntent.OnFinished onFinishedCallback =
+ (PendingIntent unusedPendingIntent,
+ Intent unusedIntent,
+ int resultCode,
+ String resultData,
+ Bundle resultExtras) -> {
+ // verify that the wakelock is held before calling the OnFinished callback.
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isTrue();
+ broker.onSendFinished(
+ unusedPendingIntent,
+ unusedIntent,
+ resultCode,
+ resultData,
+ resultExtras);
+ latch.countDown();
+ };
+
+ broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_pendingIntent_multipleTimes() throws InterruptedException {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+ ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+ CountDownLatch latch = new CountDownLatch(3);
+ PendingIntent.OnFinished onFinishedCallback =
+ (PendingIntent unusedPendingIntent,
+ Intent unusedIntent,
+ int resultCode,
+ String resultData,
+ Bundle resultExtras) -> {
+ // verify that the wakelock is held before calling the OnFinished callback.
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isTrue();
+ broker.onSendFinished(
+ unusedPendingIntent,
+ unusedIntent,
+ resultCode,
+ resultData,
+ resultExtras);
+ latch.countDown();
+ };
+
+ broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+ broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+ broker.doSendPendingIntent(pendingIntent, new Intent(), onFinishedCallback);
+
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_pendingIntent_binderDied() {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+ ContextHubClientBroker broker = createFromPendingIntent(pendingIntent);
+
+ broker.binderDied();
+
+ // The wakelock should still be usable because a pending intent exists.
+ assertThat(broker.isWakelockUsable()).isTrue();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_close_pendingIntent() {
+ ContextHubClientBroker broker = createFromCallback();
+
+ broker.close();
+
+ // The wakelock should be unusable because broker.close() clears out the pending intent.
+ assertThat(broker.isWakelockUsable()).isFalse();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+
+ @Test
+ public void testWakeLock_onNanoAppUnloaded_closeBeforeCallback() {
+ ContextHubClientBroker broker = createFromCallback();
+
+ broker.onNanoAppUnloaded(NANOAPP_ID);
+ broker.close();
+
+ assertThat(broker.isWakelockUsable()).isFalse();
+ assertThat(broker.getWakeLock().isHeld()).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS b/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS
new file mode 100644
index 0000000..5bdc0a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/OWNERS
@@ -0,0 +1,2 @@
+# Bug component ID: 156070
+include /services/core/java/com/android/server/location/contexthub/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
new file mode 100644
index 0000000..6035250
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -0,0 +1,40 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.location.contexthub."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.location.contexthub."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeGateKeeperService.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeGateKeeperService.java
index 094b7af..b044d7a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeGateKeeperService.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeGateKeeperService.java
@@ -42,7 +42,7 @@
ByteBuffer buffer = ByteBuffer.allocate(handle.length);
buffer.put(handle, 0, handle.length);
buffer.flip();
- int version = buffer.get();
+ buffer.get(); // version
sid = buffer.getLong();
password = new byte[buffer.remaining()];
buffer.get(password);
@@ -50,7 +50,7 @@
public byte[] toBytes() {
ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
- buffer.put((byte)0);
+ buffer.put((byte)0); // version
buffer.putLong(sid);
buffer.put(password);
return buffer.array();
@@ -70,14 +70,14 @@
ByteBuffer buffer = ByteBuffer.allocate(handle.length);
buffer.put(handle, 0, handle.length);
buffer.flip();
- int version = buffer.get();
+ buffer.get(); // version
challenge = buffer.getLong();
sid = buffer.getLong();
}
public byte[] toBytes() {
ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
- buffer.put((byte)0);
+ buffer.put((byte)0); // version
buffer.putLong(challenge);
buffer.putLong(sid);
return buffer.array();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
index 6de7fdd..ec708ad 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -250,8 +250,8 @@
// schedule (a) an alarm for non-strong biometric fallback timeout and (b) an alarm for
// non-strong biometric idle timeout, so later we can verify that unlocking with
// strong biometric or primary auth will cancel those alarms
- mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
- mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, userId);
+ mStrongAuth.scheduleNonStrongBiometricIdleTimeout(userId);
}
private void verifyAlarmsCancelledAndNonStrongBiometricAllowed(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 186a04f..8cb18a8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -35,7 +35,6 @@
private FakeGateKeeperService mGateKeeper;
private IWeaver mWeaverService;
- private PasswordSlotManagerTestable mPasswordSlotManager;
public MockSyntheticPasswordManager(Context context, LockSettingsStorage storage,
FakeGateKeeperService gatekeeper, UserManager userManager,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
index 0f24fb2..2faf6a2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/PasswordSlotManagerTests.java
@@ -16,8 +16,9 @@
package com.android.server.locksettings;
+import static org.junit.Assert.assertEquals;
+
import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,7 +38,7 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class PasswordSlotManagerTests extends AndroidTestCase {
+public class PasswordSlotManagerTests {
PasswordSlotManagerTestable mManager;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index b01c1c8..858f658 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -125,7 +125,6 @@
static class MockInjector extends RebootEscrowManager.Injector {
private final IRebootEscrow mRebootEscrow;
- private final ResumeOnRebootServiceConnection mServiceConnection;
private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider;
private final UserManager mUserManager;
private final MockableRebootEscrowInjected mInjected;
@@ -140,7 +139,6 @@
MockableRebootEscrowInjected injected) {
super(context, storage);
mRebootEscrow = rebootEscrow;
- mServiceConnection = null;
mServerBased = false;
RebootEscrowProviderHalImpl.Injector halInjector =
new RebootEscrowProviderHalImpl.Injector() {
@@ -161,7 +159,6 @@
LockSettingsStorageTestable storage,
MockableRebootEscrowInjected injected) {
super(context, storage);
- mServiceConnection = serviceConnection;
mRebootEscrow = null;
mServerBased = true;
RebootEscrowProviderServerBasedImpl.Injector injector =
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index c2e83f2..ea5caa8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -678,8 +678,7 @@
mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
- SecretKey applicationKey =
- addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+ addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
mKeySyncTask.run();
@@ -710,8 +709,7 @@
mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
- SecretKey applicationKey =
- addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+ addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
mKeySyncTask.run();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 3fd2c97..c546a74 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -587,7 +587,7 @@
return keyGenerator.generateKey();
}
- class PlatformKeyManagerTestable extends PlatformKeyManager {
+ static class PlatformKeyManagerTestable extends PlatformKeyManager {
private IGateKeeperService mGateKeeperService;
PlatformKeyManagerTestable(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index aceae61..281195d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -134,7 +134,6 @@
"V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
private static final String TEST_ALIAS = "nick";
private static final String TEST_ALIAS2 = "bob";
- private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
private static final int APPLICATION_KEY_SIZE_BYTES = 32;
private static final int GENERATION_ID = 1;
private static final byte[] NONCE = getUtf8Bytes("nonce");
@@ -503,8 +502,6 @@
@Test
public void initRecoveryService_throwsExceptionOnSmallerSerial() throws Exception {
- int uid = Binder.getCallingUid();
- int userId = UserHandle.getCallingUserId();
long certSerial = 1000L;
mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS,
@@ -636,7 +633,6 @@
throws Exception {
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
- long certSerial = 1000L;
mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
index 5d4be1b..4bf99e0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestData.java
@@ -39,7 +39,6 @@
private static final String KEY_ALGORITHM = "AES";
private static final long DEFAULT_SERIAL = 10001;
- private static final String CERT_PATH_ENCODING = "PkiPath";
private static final String CERT_PATH_1_BASE64 = ""
+ "MIIIXTCCBRowggMCoAMCAQICEB35ZwzVpI9ssXg9SAehnU0wDQYJKoZIhvcNAQEL"
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java
index 0b15a12..1c9c6dc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/CleanupManagerTest.java
@@ -21,11 +21,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -49,7 +47,6 @@
private static final long USER_SERIAL_NUMBER = 101L;
private static final long USER_SERIAL_NUMBER_2 = 202L;
- private Context mContext;
private CleanupManager mManager;
@Mock private RecoverableKeyStoreDb mDatabase;
@@ -60,8 +57,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContext = InstrumentationRegistry.getTargetContext();
- mManager = new CleanupManager(mContext, mRecoverySnapshotStorage, mDatabase, mUserManager,
+ mManager = new CleanupManager(mRecoverySnapshotStorage, mDatabase, mUserManager,
mApplicationKeyStorage);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
index a62569f..8715afd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -329,6 +329,27 @@
state.isInstallAllowed());
}
+ public void testAreAllVerificationsComplete_timeoutSuccessWithSufficient() {
+ PackageVerificationState state = new PackageVerificationState(null);
+
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse(state.areAllVerificationsComplete());
+ assertFalse(state.isVerificationComplete());
+ assertFalse(state.isInstallAllowed());
+
+ // Required verifier responded, but still waiting for sufficient.
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+ assertFalse(state.isVerificationComplete());
+
+ // Timeout, verification complete and installation allowed.
+ state.setVerifierResponse(REQUIRED_UID_1,
+ PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
+ assertTrue(state.isVerificationComplete());
+ assertTrue(state.isInstallAllowed());
+ }
+
public void testAreAllVerificationsComplete_onlyVerificationPasses() {
PackageVerificationState state = new PackageVerificationState(null);
state.addRequiredVerifierUid(REQUIRED_UID_1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 06b7112..5d48501 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -266,6 +266,8 @@
}
assertThat(createUser("User beyond", userType, 0)).isNull();
+
+ assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isTrue();
}
@MediumTest
@@ -467,8 +469,10 @@
@MediumTest
@Test
public void testThereCanBeOnlyOneGuest() throws Exception {
+ assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isTrue();
UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
assertThat(userInfo1).isNotNull();
+ assertThat(mUserManager.canAddMoreUsers(mUserManager.USER_TYPE_FULL_GUEST)).isFalse();
UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST);
assertThat(userInfo2).isNull();
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 7746bd6..0635cc4 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AlertDialog;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.fingerprint.FingerprintManager;
@@ -36,10 +35,13 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableResources;
import android.view.Window;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +49,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.util.List;
@@ -68,17 +69,22 @@
BiometricStateListener.STATE_BP_AUTH,
BiometricStateListener.STATE_AUTH_OTHER);
+ private static final Integer AUTO_DISMISS_DIALOG = 500;
+
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
- @Mock private PackageManager mPackageManager;
- @Mock private FingerprintManager mFingerprintManager;
- @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
- @Mock private AlertDialog mAlertDialog;
- @Mock private Window mWindow;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private SideFpsToast mDialog;
+ @Mock
+ private Window mWindow;
- private TestLooper mLooper = new TestLooper();
+ private final TestLooper mLooper = new TestLooper();
private SideFpsEventHandler mEventHandler;
private BiometricStateListener mBiometricStateListener;
@@ -88,16 +94,17 @@
mContext.addMockSystemService(PackageManager.class, mPackageManager);
mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_sideFpsToastTimeout, AUTO_DISMISS_DIALOG);
- when(mDialogBuilder.create()).thenReturn(mAlertDialog);
- when(mAlertDialog.getWindow()).thenReturn(mWindow);
+ when(mDialog.getWindow()).thenReturn(mWindow);
mEventHandler =
new SideFpsEventHandler(
mContext,
new Handler(mLooper.getLooper()),
mContext.getSystemService(PowerManager.class),
- () -> mDialogBuilder);
+ (ctx) -> mDialog);
}
@Test
@@ -108,7 +115,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -120,7 +127,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
}
@@ -133,7 +140,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
}
@@ -148,7 +155,7 @@
assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -159,18 +166,18 @@
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
- public void promptsWhenBPisActive() throws Exception {
+ public void doesNotpromptWhenBPisActive() throws Exception {
setupWithSensor(true /* hasSideFps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_BP_AUTH);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog, never()).show();
+ verify(mDialog, never()).show();
}
@Test
@@ -181,57 +188,40 @@
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
- verify(mAlertDialog, never()).dismiss();
+ verify(mDialog).show();
}
@Test
- public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
+ public void dialogDismissesAfterTime() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
+ when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
-
- mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- mLooper.moveTimeForward(10000);
+ verify(mDialog).show();
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG);
mLooper.dispatchAll();
-
- verify(mAlertDialog).dismiss();
+ verify(mDialog).dismiss();
}
@Test
- public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
+ public void dialogDoesNotDismissOnSensorTouch() throws Exception {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
+ when(mDialog.isShowing()).thenReturn(true);
assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mDialog).show();
mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
+ mLooper.moveTimeForward(AUTO_DISMISS_DIALOG - 1);
mLooper.dispatchAll();
- verify(mAlertDialog, never()).dismiss();
- }
- @Test
- public void showDialogAfterTap() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
-
- setBiometricState(BiometricStateListener.STATE_ENROLLING);
- when(mAlertDialog.isShowing()).thenReturn(true);
- mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
-
- mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mDialog, never()).dismiss();
}
private void setBiometricState(@BiometricStateListener.State int newState) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index c5117bb..e2c94c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -31,9 +32,11 @@
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
+import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
@@ -53,6 +56,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
@@ -68,31 +73,40 @@
public class ContentRecorderTests extends WindowTestsBase {
private static final IBinder TEST_TOKEN = new RecordingTestToken();
private static IBinder sTaskWindowContainerToken;
+ private Task mTask;
private final ContentRecordingSession mDisplaySession =
ContentRecordingSession.createDisplaySession(TEST_TOKEN);
private ContentRecordingSession mTaskSession;
private static Point sSurfaceSize;
private ContentRecorder mContentRecorder;
+ @Mock private ContentRecorder.MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
private SurfaceControl mRecordedSurface;
// Handle feature flag.
private ConfigListener mConfigListener;
private CountDownLatch mLatch;
+ private VirtualDisplay mVirtualDisplay;
+
@Before public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
// GIVEN SurfaceControl can successfully mirror the provided surface.
sSurfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
+ doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
// GIVEN the VirtualDisplay associated with the session (so the display has state ON).
- VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
+ mVirtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
sSurfaceSize.x, sSurfaceSize.y,
DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
- final int displayId = virtualDisplay.getDisplay().getDisplayId();
+ final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
final DisplayContent virtualDisplayContent = mWm.mRoot.getDisplayContent(displayId);
- mContentRecorder = new ContentRecorder(virtualDisplayContent);
+ mContentRecorder = new ContentRecorder(virtualDisplayContent,
+ mMediaProjectionManagerWrapper);
spyOn(virtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -116,6 +130,8 @@
@After
public void teardown() {
DeviceConfig.removeOnPropertiesChangedListener(mConfigListener);
+ mVirtualDisplay.release();
+ mWm.mRoot.onDisplayRemoved(mVirtualDisplay.getDisplay().getDisplayId());
}
@Test
@@ -179,7 +195,7 @@
mContentRecorder.setContentRecordingSession(session);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
- // TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
+ verify(mMediaProjectionManagerWrapper).stopActiveProjection();
}
@Test
@@ -190,7 +206,7 @@
mContentRecorder.setContentRecordingSession(invalidTaskSession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
- // TODO(b/219761722) validate VirtualDisplay is torn down when can't set up task recording.
+ verify(mMediaProjectionManagerWrapper).stopActiveProjection();
}
@Test
@@ -218,9 +234,24 @@
mContentRecorder.updateRecording();
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
- verify(mTransaction, atLeastOnce()).setPosition(eq(mRecordedSurface), anyFloat(),
+ verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
anyFloat());
- verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testOnTaskConfigurationChanged_resizesSurface() {
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+
+ Configuration config = mTask.getConfiguration();
+ config.orientation = ORIENTATION_PORTRAIT;
+ mTask.onConfigurationChanged(config);
+
+ verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
anyFloat(), anyFloat());
}
@@ -240,21 +271,31 @@
}
@Test
- public void testRemove_stopsRecording() {
+ public void testStopRecording_stopsRecording() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
- mContentRecorder.remove();
+ mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
- public void testRemove_neverRecording() {
- mContentRecorder.remove();
+ public void testStopRecording_neverRecording() {
+ mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
+ public void testRemoveTask_stopsRecording() {
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+
+ mTask.removeImmediately();
+
+ verify(mMediaProjectionManagerWrapper).stopActiveProjection();
+ }
+
+ @Test
public void testUpdateMirroredSurface_capturedAreaResized() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -297,10 +338,10 @@
*/
private IBinder setUpTaskWindowContainerToken(DisplayContent displayContent) {
final Task rootTask = createTask(displayContent);
- final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ mTask = createTaskInRootTask(rootTask, 0 /* userId */);
// Ensure the task is not empty.
- createActivityRecord(displayContent, task);
- return task.getTaskInfo().token.asBinder();
+ createActivityRecord(displayContent, mTask);
+ return mTask.getTaskInfo().token.asBinder();
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 7698033d..342d68b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -87,7 +87,7 @@
@Test
public void testSetContentRecordingSessionLocked_invalidToken_notAccepted() {
ContentRecordingController controller = new ContentRecordingController();
- // GIVEN an invalid display session (null token).
+ // GIVEN a session with a null token.
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(null);
session.setDisplayId(DEFAULT_DISPLAY);
// WHEN updating the session.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 641a3ad..28d2aa1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -31,6 +31,7 @@
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.InsetsState.ITYPE_IME;
@@ -1251,7 +1252,15 @@
public void testComputeImeControlTarget() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setRemoteInsetsController(createDisplayWindowInsetsController());
- dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
+ dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app");
+
+ // Expect returning null IME control target when the focus window has not yet been the
+ // IME input target (e.g. IME is restarting) in fullscreen windowing mode.
+ dc.setImeInputTarget(null);
+ assertFalse(dc.mCurrentFocus.inMultiWindowMode());
+ assertNull(dc.computeImeControlTarget());
+
+ dc.setImeInputTarget(dc.mCurrentFocus);
dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState());
assertEquals(dc.getImeInputTarget().getWindowState(), dc.computeImeControlTarget());
}
@@ -2588,6 +2597,43 @@
display.release();
}
+ @Test
+ public void testVirtualDisplayContent_displayMirroring() {
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ surfaceControlMirrors(surfaceSize);
+ // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
+ // call in the test. We need to spy on the DC before updateRecording is called or we can't
+ // verify setDisplayMirroring is called
+ doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
+ // GIVEN a new VirtualDisplay with an associated surface.
+ final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
+ final int displayId = display.getDisplay().getDisplayId();
+
+ // GIVEN a session for this display.
+ mWm.mRoot.onDisplayAdded(displayId);
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
+ spyOn(actualDC);
+ // Return the default display as the value to mirror to ensure the VD with flag mirroring
+ // creates a ContentRecordingSession automatically.
+ doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ actualDC.updateRecording();
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ verify(actualDC).setDisplayMirroring();
+ assertThat(actualDC.isCurrentlyRecording()).isTrue();
+ display.release();
+ }
+
private static class MirroringTestToken extends Binder {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 21839aa..6d33aaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -97,12 +97,12 @@
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
-import org.mockito.Mockito;
/**
* Tests for Size Compatibility mode.
@@ -1538,6 +1538,108 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(1.1f)
+ .setUid(android.os.Process.myUid())
+ .build();
+ // Setup Letterbox Configuration
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+ Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(1.1f)
+ .setUid(android.os.Process.myUid())
+ .build();
+ // Setup Letterbox Configuration
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+ Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+ public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(1.1f)
+ .setUid(android.os.Process.myUid())
+ .build();
+ // Setup Letterbox Configuration
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+ Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+ public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(1.1f)
+ .setUid(android.os.Process.myUid())
+ .build();
+ // Setup Letterbox Configuration
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ activity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
+ // Non-resizable portrait activity
+ prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+ Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
public void testSplitAspectRatioForUnresizableLandscapeApps() {
// Set up a display in portrait and ignoring orientation request.
int screenWidth = 1400;
@@ -2165,6 +2267,40 @@
}
@Test
+ public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
+ .setNotch(100)
+ .build();
+ setUpApp(display);
+ TestWindowState statusBar = addStatusBar(mActivity.mDisplayContent);
+ spyOn(statusBar);
+ doReturn(new Rect(0, 0, statusBar.mRequestedWidth, statusBar.mRequestedHeight))
+ .when(statusBar).getFrame();
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ // Refresh the letterbox
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(0, 750, 1000, 1950));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+ mBounds,
+ mActivity.getDisplayContent().getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
@@ -2785,7 +2921,7 @@
return w;
}
- private static void addStatusBar(DisplayContent displayContent) {
+ private static TestWindowState addStatusBar(DisplayContent displayContent) {
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
doReturn(true).when(displayPolicy).hasStatusBar();
displayPolicy.onConfigurationChanged();
@@ -2806,6 +2942,7 @@
displayPolicy.addWindowLw(statusBar, attrs);
displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames);
+ return statusBar;
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 9693532..a62625c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -578,6 +578,22 @@
}
@Test
+ public void testContainerTranslucentChanges() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertFalse(rootTask.isTranslucent(activity));
+ t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), true);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ assertTrue(rootTask.isTranslucent(activity));
+ t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), false);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ assertFalse(rootTask.isTranslucent(activity));
+ }
+
+ @Test
public void testSetIgnoreOrientationRequest_taskDisplayArea() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 85b9f75..9562c1a 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -508,6 +508,7 @@
// current USB state
private boolean mHostConnected;
private boolean mUsbAccessoryConnected;
+ private boolean mInHostModeWithNoAccessoryConnected;
private boolean mSourcePower;
private boolean mSinkPower;
private boolean mConfigured;
@@ -959,6 +960,17 @@
mSupportsAllCombinations = false;
}
+ if (mHostConnected) {
+ if (!mUsbAccessoryConnected) {
+ mInHostModeWithNoAccessoryConnected = true;
+ } else {
+ mInHostModeWithNoAccessoryConnected = false;
+ }
+ } else {
+ // if not in host mode, reset value to false
+ mInHostModeWithNoAccessoryConnected = false;
+ }
+
mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY);
args.recycle();
@@ -983,6 +995,12 @@
Slog.i(TAG, "HOST_STATE connected:" + mUsbAccessoryConnected);
}
+ if (!devices.hasNext()) {
+ mInHostModeWithNoAccessoryConnected = true;
+ } else {
+ mInHostModeWithNoAccessoryConnected = false;
+ }
+
mHideUsbNotification = false;
while (devices.hasNext()) {
Map.Entry pair = (Map.Entry) devices.next();
@@ -1192,7 +1210,8 @@
// Dont show the notification when connected to a USB peripheral
// and the link does not support PR_SWAP and DR_SWAP
- if (mHideUsbNotification && !mSupportsAllCombinations) {
+ if ((mHideUsbNotification || mInHostModeWithNoAccessoryConnected)
+ && !mSupportsAllCombinations) {
if (mUsbNotificationId != 0) {
mNotificationManager.cancelAsUser(null, mUsbNotificationId,
UserHandle.ALL);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1e0e836..a131084 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -18,6 +18,8 @@
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
+import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
@@ -58,6 +60,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.attention.AttentionManagerInternal;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -186,6 +189,12 @@
final int mUser;
final Context mContext;
+ @Nullable final AttentionManagerInternal mAttentionManagerInternal;
+
+ final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
+ this::setProximityMeters;
+
+
volatile HotwordDetectionServiceIdentity mIdentity;
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
private Instant mLastRestartInstant;
@@ -206,6 +215,8 @@
private @NonNull ServiceConnection mRemoteHotwordDetectionService;
private IBinder mAudioFlinger;
private boolean mDebugHotwordLogging = false;
+ @GuardedBy("mLock")
+ private double mProximityMeters = PROXIMITY_UNKNOWN;
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -233,6 +244,10 @@
mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+ mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+ if (mAttentionManagerInternal != null) {
+ mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+ }
mLastRestartInstant = Instant.now();
updateStateAfterProcessStart(options, sharedMemory);
@@ -397,6 +412,9 @@
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
}
+ if (mAttentionManagerInternal != null) {
+ mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
+ }
}
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
@@ -464,6 +482,7 @@
mSoftwareCallback.onError();
return;
}
+ saveProximityMetersToBundle(result);
mSoftwareCallback.onDetected(result, null, null);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -591,6 +610,7 @@
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
+ saveProximityMetersToBundle(result);
externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -1159,6 +1179,20 @@
});
}
+ private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+ synchronized (mLock) {
+ if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
+ result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+ }
+ }
+ }
+
+ private void setProximityMeters(double proximityMeters) {
+ synchronized (mLock) {
+ mProximityMeters = proximityMeters;
+ }
+ }
+
private static void bestEffortClose(Closeable... closeables) {
for (Closeable closeable : closeables) {
bestEffortClose(closeable);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1f97d86..f28408e1a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -194,13 +194,24 @@
}
@Override
- public T recompute(Void aVoid) {
+ public T recompute(Void query) {
+ // This always throws on any error. The exceptions must be handled outside
+ // the cache.
+ try {
+ return mInterfaceMethod.applyOrThrow(TelephonyManager.getSubscriptionService());
+ } catch (Exception re) {
+ throw new RuntimeException(re);
+ }
+ }
+
+ @Override
+ public T query(Void query) {
T result = mDefaultValue;
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- result = mInterfaceMethod.applyOrThrow(iSub);
+ result = super.query(query);
}
} catch (Exception ex) {
Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty);
@@ -229,12 +240,24 @@
@Override
public T recompute(Integer query) {
+ // This always throws on any error. The exceptions must be handled outside
+ // the cache.
+ try {
+ return mInterfaceMethod.applyOrThrow(
+ TelephonyManager.getSubscriptionService(), query);
+ } catch (Exception re) {
+ throw new RuntimeException(re);
+ }
+ }
+
+ @Override
+ public T query(Integer query) {
T result = mDefaultValue;
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- result = mInterfaceMethod.applyOrThrow(iSub, query);
+ result = super.query(query);
}
} catch (Exception ex) {
Rlog.w(LOG_TAG, "Failed to recompute cache key for " + mCacheKeyProperty);
@@ -4118,4 +4141,3 @@
usageSetting, subscriptionId, mContext.getOpPackageName()));
}
}
-
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a7468a9..7893992 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6641,7 +6641,7 @@
* list will be provided. If an error occurs, null will be provided unless the onError
* callback is overridden.
*
- * @param cellInfo a list of {@link CellInfo}, an empty list, or null.
+ * @param cellInfo a list of {@link CellInfo} or an empty list.
*
* {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
*/
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 3ed87e1..f794a79 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -24,7 +24,6 @@
import android.database.Cursor;
import android.hardware.radio.V1_5.ApnTypes;
import android.net.Uri;
-import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Telephony;
@@ -964,7 +963,7 @@
ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
}
int mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU_V4));
- if (mtuV4 == -1) {
+ if (mtuV4 == UNSET_MTU) {
mtuV4 = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU));
}
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 531dd7c..35f076f 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -6,6 +6,8 @@
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
+ method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+ method public void clearCrossProfileIntentFilters(int);
method public int getInstallReason(String, android.os.UserHandle);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
method public String[] getNamesForUids(int[]);
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index fecc7b3..d02fd83 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1028,7 +1028,6 @@
// These permissions are required by services implementing services
// the system binds to (IME, Accessibility, PrintServices, etc.)
bool hasBindDeviceAdminPermission = false;
- bool hasBindInputMethodPermission = false;
bool hasBindAccessibilityServicePermission = false;
bool hasBindPrintServicePermission = false;
bool hasBindNfcServicePermission = false;
@@ -1757,7 +1756,6 @@
hasMetaHostPaymentCategory = false;
hasMetaOffHostPaymentCategory = false;
hasBindDeviceAdminPermission = false;
- hasBindInputMethodPermission = false;
hasBindAccessibilityServicePermission = false;
hasBindPrintServicePermission = false;
hasBindNfcServicePermission = false;
@@ -1871,9 +1869,7 @@
String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR,
&error);
if (error == "") {
- if (permission == "android.permission.BIND_INPUT_METHOD") {
- hasBindInputMethodPermission = true;
- } else if (permission ==
+ if (permission ==
"android.permission.BIND_ACCESSIBILITY_SERVICE") {
hasBindAccessibilityServicePermission = true;
} else if (permission ==
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index b9de11b..47750fc 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2970,14 +2970,6 @@
}
e->setNameIndex(keyStrings.add(e->getName(), true));
- // If this entry has no values for other configs,
- // and is the default config, then it is special. Otherwise
- // we want to add it with the config info.
- ConfigDescription* valueConfig = NULL;
- if (N != 1 || config == nullConfig) {
- valueConfig = &config;
- }
-
status_t err = e->prepareFlatten(&valueStrings, this,
&configTypeName, &config);
if (err != NO_ERROR) {
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index cd07b1e..46a846b 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -641,7 +641,23 @@
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
// resource isn't exempt from collapse, add it as obfuscated value
- local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ if (entry.overlayable_item) {
+ // if the resource name of the specific entry is obfuscated and this
+ // entry is in the overlayable list, the overlay can't work on this
+ // overlayable at runtime because the name has been obfuscated in
+ // resources.arsc during flatten operation.
+ const OverlayableItem& item = entry.overlayable_item.value();
+ context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
+ << "The resource name of overlayable entry "
+ << resource_name.to_string() << "'"
+ << " shouldn't be obfuscated in resources.arsc");
+
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+ } else {
+ // TODO(b/228192695): output the entry.name and Resource id to make
+ // de-obfuscated possible.
+ local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ }
}
// Group values by configuration.
for (auto& config_value : entry.values) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 1dd2468..e48fca6 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -878,4 +878,52 @@
Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
}
+TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseNotInExemption) {
+ OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+ overlayable_item.policies |= PolicyFlags::PUBLIC;
+
+ std::string name = "com.app.test:color/overlayable_color";
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8),
+ 0xffaabbcc))
+ .SetOverlayable(name, overlayable_item)
+ .Build();
+
+ TableFlattenerOptions options;
+ options.collapse_key_stringpool = true;
+
+ ResTable res_table;
+ EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue());
+ EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {},
+ Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u),
+ testing::IsTrue());
+}
+
+TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) {
+ OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+ overlayable_item.policies |= PolicyFlags::PUBLIC;
+
+ std::string name = "com.app.test:color/overlayable_color";
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000),
+ util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8),
+ 0xffaabbcc))
+ .SetOverlayable(name, overlayable_item)
+ .Build();
+
+ TableFlattenerOptions options;
+ options.collapse_key_stringpool = true;
+ options.name_collapse_exemptions.insert(
+ ResourceName({}, ResourceType::kColor, "overlayable_color"));
+
+ ResTable res_table;
+ EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue());
+ EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {},
+ Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u),
+ testing::IsTrue());
+}
+
} // namespace aapt
diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py
deleted file mode 100644
index 15088fc..0000000
--- a/tools/stringslint/stringslint.py
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/env python3
-#-*- coding: utf-8 -*-
-
-# 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.
-
-"""
-Enforces common Android string best-practices. It ignores lint messages from
-a previous strings file, if provided.
-
-Usage: stringslint.py strings.xml
-Usage: stringslint.py strings.xml old_strings.xml
-
-In general:
-* Errors signal issues that must be fixed before submitting, and are only
- used when there are no false-positives.
-* Warnings signal issues that might need to be fixed, but need manual
- inspection due to risk of false-positives.
-* Info signal issues that should be fixed to match best-practices, such
- as providing comments to aid translation.
-"""
-
-import re, sys, codecs
-import lxml.etree as ET
-
-BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
-
-def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
- # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
- codes = []
- if reset: codes.append("0")
- else:
- if not fg is None: codes.append("3%d" % (fg))
- if not bg is None:
- if not bright: codes.append("4%d" % (bg))
- else: codes.append("10%d" % (bg))
- if bold: codes.append("1")
- elif dim: codes.append("2")
- else: codes.append("22")
- return "\033[%sm" % (";".join(codes))
-
-warnings = None
-
-def warn(tag, msg, actual, expected, color=YELLOW):
- global warnings
- key = "%s:%d" % (tag.attrib["name"], hash(msg))
- value = "%sLine %d: '%s':%s %s" % (format(fg=color, bold=True),
- tag.sourceline,
- tag.attrib["name"],
- format(reset=True),
- msg)
- if not actual is None: value += "\n\tActual: %s%s%s" % (format(dim=True),
- actual,
- format(reset=True))
- if not expected is None: value += "\n\tExample: %s%s%s" % (format(dim=True),
- expected,
- format(reset=True))
- warnings[key] = value
-
-
-def error(tag, msg, actual, expected):
- warn(tag, msg, actual, expected, RED)
-
-def info(tag, msg, actual, expected):
- warn(tag, msg, actual, expected, CYAN)
-
-# Escaping logic borrowed from https://stackoverflow.com/a/24519338
-ESCAPE_SEQUENCE_RE = re.compile(r'''
- ( \\U........ # 8-digit hex escapes
- | \\u.... # 4-digit hex escapes
- | \\x.. # 2-digit hex escapes
- | \\[0-7]{1,3} # Octal escapes
- | \\N\{[^}]+\} # Unicode characters by name
- | \\[\\'"abfnrtv] # Single-character escapes
- )''', re.UNICODE | re.VERBOSE)
-
-def decode_escapes(s):
- def decode_match(match):
- return codecs.decode(match.group(0), 'unicode-escape')
-
- s = re.sub(r"\n\s*", " ", s)
- s = ESCAPE_SEQUENCE_RE.sub(decode_match, s)
- s = re.sub(r"%(\d+\$)?[a-z]", "____", s)
- s = re.sub(r"\^\d+", "____", s)
- s = re.sub(r"<br/?>", "\n", s)
- s = re.sub(r"</?[a-z]+>", "", s)
- return s
-
-def sample_iter(tag):
- if not isinstance(tag, ET._Comment) and re.match("{.*xliff.*}g", tag.tag) and "example" in tag.attrib:
- yield tag.attrib["example"]
- elif tag.text:
- yield decode_escapes(tag.text)
- for e in tag:
- for v in sample_iter(e):
- yield v
- if e.tail:
- yield decode_escapes(e.tail)
-
-def lint(path):
- global warnings
- warnings = {}
-
- with open(path) as f:
- raw = f.read()
- if len(raw.strip()) == 0:
- return warnings
- tree = ET.fromstring(bytes(raw, encoding='utf-8'))
- root = tree #tree.getroot()
-
- last_comment = None
- for child in root:
- # TODO: handle plurals
- if isinstance(child, ET._Comment):
- last_comment = child
- elif child.tag == "string":
- # We always consume comment
- comment = last_comment
- last_comment = None
-
- # Prepare string for analysis
- text = "".join(child.itertext())
- sample = "".join(sample_iter(child)).strip().strip("'\"")
-
- # Validate comment
- if comment is None:
- info(child, "Missing string comment to aid translation",
- None, None)
- continue
- if "do not translate" in comment.text.lower():
- continue
- if "translatable" in child.attrib and child.attrib["translatable"].lower() == "false":
- continue
-
- misspelled_attributes = [
- ("translateable", "translatable"),
- ]
- for misspelling, expected in misspelled_attributes:
- if misspelling in child.attrib:
- error(child, "Misspelled <string> attribute.", misspelling, expected)
-
- limit = re.search("CHAR[ _-]LIMIT=(\d+|NONE|none)", comment.text)
- if limit is None:
- info(child, "Missing CHAR LIMIT to aid translation",
- repr(comment), "<!-- Description of string [CHAR LIMIT=32] -->")
- elif re.match("\d+", limit.group(1)):
- limit = int(limit.group(1))
- if len(sample) > limit:
- warn(child, "Expanded string length is larger than CHAR LIMIT",
- sample, None)
-
- # Look for common mistakes/substitutions
- if "'" in text:
- error(child, "Turned quotation mark glyphs are more polished",
- text, "This doesn\u2019t need to \u2018happen\u2019 today")
- if '"' in text and not text.startswith('"') and text.endswith('"'):
- error(child, "Turned quotation mark glyphs are more polished",
- text, "This needs to \u201chappen\u201d today")
- if "..." in text:
- error(child, "Ellipsis glyph is more polished",
- text, "Loading\u2026")
- if "wi-fi" in text.lower():
- error(child, "Non-breaking glyph is more polished",
- text, "Wi\u2011Fi")
- if "wifi" in text.lower():
- error(child, "Using non-standard spelling",
- text, "Wi\u2011Fi")
- if re.search("\d-\d", text):
- warn(child, "Ranges should use en dash glyph",
- text, "You will find this material in chapters 8\u201312")
- if "--" in text:
- warn(child, "Phrases should use em dash glyph",
- text, "Upon discovering errors\u2014all 124 of them\u2014they recalled.")
- if ". " in text:
- warn(child, "Only use single space between sentences",
- text, "First idea. Second idea.")
- if re.match(r"^[A-Z\s]{5,}$", text):
- warn(child, "Actions should use android:textAllCaps in layout; ignore if acronym",
- text, "Refresh data")
- if " phone " in text and "product" not in child.attrib:
- warn(child, "Strings mentioning phones should have variants for tablets",
- text, None)
-
- # When more than one substitution, require indexes
- if len(re.findall("%[^%]", text)) > 1:
- if len(re.findall("%[^\d]", text)) > 0:
- error(child, "Substitutions must be indexed",
- text, "Add %1$s to %2$s")
-
- # Require xliff substitutions
- for gc in child.iter():
- badsub = False
- if gc.tail and re.search("%[^%]", gc.tail): badsub = True
- if re.match("{.*xliff.*}g", gc.tag):
- if "id" not in gc.attrib:
- error(child, "Substitutions must define id attribute",
- None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
- if "example" not in gc.attrib:
- error(child, "Substitutions must define example attribute",
- None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
- else:
- if gc.text and re.search("%[^%]", gc.text): badsub = True
- if badsub:
- error(child, "Substitutions must be inside xliff tags",
- text, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
-
- return warnings
-
-if len(sys.argv) > 2:
- before = lint(sys.argv[2])
-else:
- before = {}
-after = lint(sys.argv[1])
-
-for b in before:
- if b in after:
- del after[b]
-
-if len(after) > 0:
- for a in sorted(after.keys()):
- print(after[a])
- print()
- sys.exit(1)
diff --git a/tools/stringslint/stringslint_sha.sh b/tools/stringslint/stringslint_sha.sh
index bd05698..009a1f2 100755
--- a/tools/stringslint/stringslint_sha.sh
+++ b/tools/stringslint/stringslint_sha.sh
@@ -1,5 +1,3 @@
#!/bin/bash
-LOCAL_DIR="$( dirname ${BASH_SOURCE} )"
-git show --name-only --pretty=format: $1 | grep values/strings.xml | while read file; do
- python3 $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file)
-done
+
+# NOTE: this script has been deprecated and replaced by AyeAye checks directly in Gerrit