Merge changes Ia239ec9b,I7eaf5cda into main
* changes:
Ignore keyguard transitions FROM an invalid state.
Add currentTransitionInfo + filterRelevantKeyguardState.
diff --git a/Android.bp b/Android.bp
index 59e903e..f6bfe65 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,7 +389,6 @@
// TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
// system propagates "required" properly.
"gps_debug.conf",
- "core.protolog.pb",
"framework-res",
// any install dependencies should go into framework-minus-apex-install-dependencies
// rather than here to avoid bloating incremental build time
diff --git a/Ravenwood.bp b/Ravenwood.bp
index f43c37b..c3b22c4 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -30,7 +30,7 @@
name: "framework-minus-apex.ravenwood-base",
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
- "@$(location ravenwood/ravenwood-standard-options.txt) " +
+ "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
"--debug-log $(location hoststubgen_framework-minus-apex.log) " +
"--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
@@ -41,13 +41,13 @@
"--gen-input-dump-file $(location hoststubgen_dump.txt) " +
"--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
- "--policy-override-file $(location ravenwood/framework-minus-apex-ravenwood-policies.txt) " +
- "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+ "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
srcs: [
":framework-minus-apex-for-hoststubgen",
- "ravenwood/framework-minus-apex-ravenwood-policies.txt",
- "ravenwood/ravenwood-standard-options.txt",
- "ravenwood/ravenwood-annotation-allowed-classes.txt",
+ "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt",
+ "ravenwood/texts/ravenwood-standard-options.txt",
+ "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
],
out: [
"ravenwood.jar",
@@ -77,6 +77,19 @@
],
}
+// Extract the stats file.
+genrule {
+ name: "framework-minus-apex.ravenwood.stats",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
+ ],
+ out: [
+ "hoststubgen_framework-minus-apex_stats.csv",
+ ],
+}
+
java_library {
name: "services.core-for-hoststubgen",
installable: false, // host only jar.
@@ -91,7 +104,7 @@
name: "services.core.ravenwood-base",
tools: ["hoststubgen"],
cmd: "$(location hoststubgen) " +
- "@$(location ravenwood/ravenwood-standard-options.txt) " +
+ "@$(location ravenwood/texts/ravenwood-standard-options.txt) " +
"--debug-log $(location hoststubgen_services.core.log) " +
"--stats-file $(location hoststubgen_services.core_stats.csv) " +
@@ -102,13 +115,13 @@
"--gen-input-dump-file $(location hoststubgen_dump.txt) " +
"--in-jar $(location :services.core-for-hoststubgen) " +
- "--policy-override-file $(location ravenwood/services.core-ravenwood-policies.txt) " +
- "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+ "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ",
srcs: [
":services.core-for-hoststubgen",
- "ravenwood/services.core-ravenwood-policies.txt",
- "ravenwood/ravenwood-standard-options.txt",
- "ravenwood/ravenwood-annotation-allowed-classes.txt",
+ "ravenwood/texts/services.core-ravenwood-policies.txt",
+ "ravenwood/texts/ravenwood-standard-options.txt",
+ "ravenwood/texts/ravenwood-annotation-allowed-classes.txt",
],
out: [
"ravenwood.jar",
@@ -135,6 +148,19 @@
],
}
+// Extract the stats file.
+genrule {
+ name: "services.core.ravenwood.stats",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
+ ],
+ out: [
+ "hoststubgen_services.core_stats.csv",
+ ],
+}
+
java_library {
name: "services.core.ravenwood-jarjar",
installable: false,
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 d0a1b02..154b2d7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -290,6 +290,8 @@
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
+
+ private boolean mStartUserBeforeScheduledAlarms;
private long mNextWakeup;
private long mNextNonWakeup;
private long mNextWakeUpSetAt;
@@ -1382,6 +1384,7 @@
@GuardedBy("mLock")
AlarmStore mAlarmStore;
+ UserWakeupStore mUserWakeupStore;
// set to non-null if in idle mode; while in this mode, any alarms we don't want
// to run during this time are rescehduled to go off after this alarm.
Alarm mPendingIdleUntil = null;
@@ -1882,6 +1885,7 @@
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
+ mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms();
if (mUseFrozenStateToDropListenerAlarms) {
final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
final int size = frozenStates.length;
@@ -2000,6 +2004,10 @@
Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
}
}
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore = new UserWakeupStore();
+ mUserWakeupStore.init();
+ }
publishLocalService(AlarmManagerInternal.class, new LocalService());
publishBinderService(Context.ALARM_SERVICE, mService);
}
@@ -2041,6 +2049,9 @@
public void onUserStarting(TargetUser user) {
super.onUserStarting(user);
final int userId = user.getUserIdentifier();
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore.onUserStarting(userId);
+ }
mHandler.post(() -> {
for (final int appId : mExactAlarmCandidates) {
final int uid = UserHandle.getUid(userId, appId);
@@ -3150,6 +3161,9 @@
pw.increaseIndent();
pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
mUseFrozenStateToDropListenerAlarms);
+ pw.println();
+ pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
+ mStartUserBeforeScheduledAlarms);
pw.decreaseIndent();
pw.println();
pw.println();
@@ -3398,6 +3412,12 @@
pw.println("]");
pw.println();
+ if (mStartUserBeforeScheduledAlarms) {
+ pw.println("Scheduled user wakeups:");
+ mUserWakeupStore.dump(pw, nowELAPSED);
+ pw.println();
+ }
+
pw.println("App Alarm history:");
mAppWakeupHistory.dump(pw, nowELAPSED);
@@ -3945,10 +3965,19 @@
formatNextAlarm(getContext(), alarmClock, userId));
}
mNextAlarmClockForUser.put(userId, alarmClock);
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
+ mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+ }
} else {
if (DEBUG_ALARM_CLOCK) {
Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): None");
}
+ if (mStartUserBeforeScheduledAlarms) {
+ if (mActivityManagerInternal.isUserRunning(userId, 0)) {
+ mUserWakeupStore.removeUserWakeup(userId);
+ }
+ }
mNextAlarmClockForUser.remove(userId);
}
@@ -4003,13 +4032,20 @@
DateFormat.format(pattern, info.getTriggerTime()).toString();
}
+ @GuardedBy("mLock")
void rescheduleKernelAlarmsLocked() {
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
final long nowElapsed = mInjector.getElapsedRealtimeMillis();
long nextNonWakeup = 0;
if (mAlarmStore.size() > 0) {
- final long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
+ long firstWakeup = mAlarmStore.getNextWakeupDeliveryTime();
+ if (mStartUserBeforeScheduledAlarms) {
+ final long firstUserWakeup = mUserWakeupStore.getNextWakeupTime();
+ if (firstUserWakeup >= 0 && firstUserWakeup < firstWakeup) {
+ firstWakeup = firstUserWakeup;
+ }
+ }
final long first = mAlarmStore.getNextDeliveryTime();
if (firstWakeup != 0) {
mNextWakeup = firstWakeup;
@@ -4716,6 +4752,16 @@
+ ", elapsed=" + nowELAPSED);
}
+ if (mStartUserBeforeScheduledAlarms) {
+ final int[] userIds =
+ mUserWakeupStore.getUserIdsToWakeup(nowELAPSED);
+ for (int i = 0; i < userIds.length; i++) {
+ if (!mActivityManagerInternal.startUserInBackground(
+ userIds[i])) {
+ mUserWakeupStore.removeUserWakeup(userIds[i]);
+ }
+ }
+ }
mLastTrigger = nowELAPSED;
final int wakeUps = triggerAlarmsLocked(triggerList, nowELAPSED);
if (wakeUps == 0 && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
@@ -5164,6 +5210,10 @@
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
sdFilter.addAction(Intent.ACTION_USER_STOPPED);
+ if (mStartUserBeforeScheduledAlarms) {
+ sdFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+ sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+ }
sdFilter.addAction(Intent.ACTION_UID_REMOVED);
getContext().registerReceiverForAllUsers(this, sdFilter,
/* broadcastPermission */ null, /* scheduler */ null);
@@ -5194,6 +5244,22 @@
mTemporaryQuotaReserve.removeForUser(userHandle);
}
return;
+ case Intent.ACTION_LOCKED_BOOT_COMPLETED:
+ final int handle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (handle >= 0) {
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore.onUserStarted(handle);
+ }
+ }
+ return;
+ case Intent.ACTION_USER_REMOVED:
+ final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (user >= 0) {
+ if (mStartUserBeforeScheduledAlarms) {
+ mUserWakeupStore.onUserRemoved(user);
+ }
+ }
+ return;
case Intent.ACTION_UID_REMOVED:
mLastPriorityAlarmDispatch.delete(uid);
mRemovalHistory.delete(uid);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
new file mode 100644
index 0000000..a0d9133
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.alarm;
+
+
+import android.annotation.Nullable;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseLongArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * User wakeup store keeps the list of user ids with the times that user needs to be started in
+ * sorted list in order for alarms to execute even if user gets stopped.
+ * The list of user ids with at least one alarms scheduled is also persisted to the XML file to
+ * start them after the device reboot.
+ */
+public class UserWakeupStore {
+ private static final boolean DEBUG = false;
+
+ static final String USER_WAKEUP_TAG = UserWakeupStore.class.getSimpleName();
+ private static final String TAG_USERS = "users";
+ private static final String TAG_USER = "user";
+ private static final String ATTR_USER_ID = "user_id";
+ private static final String ATTR_VERSION = "version";
+
+ public static final int XML_VERSION_CURRENT = 1;
+ @VisibleForTesting
+ static final String ROOT_DIR_NAME = "alarms";
+ @VisibleForTesting
+ static final String USERS_FILE_NAME = "usersWithAlarmClocks.xml";
+
+ /**
+ * Time offset of user start before the original alarm time in milliseconds.
+ * Also used to schedule user start after reboot to avoid starting them simultaneously.
+ */
+ @VisibleForTesting
+ static final long BUFFER_TIME_MS = TimeUnit.SECONDS.toMillis(30);
+ /**
+ * Maximum time deviation limit to introduce a 5-second time window for user starts.
+ */
+ @VisibleForTesting
+ static final long USER_START_TIME_DEVIATION_LIMIT_MS = TimeUnit.SECONDS.toMillis(5);
+ /**
+ * Delay between two consecutive user starts scheduled during user wakeup store initialization.
+ */
+ @VisibleForTesting
+ static final long INITIAL_USER_START_SCHEDULING_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
+
+ private final Object mUserWakeupLock = new Object();
+
+ /**
+ * A list of wakeups for users with scheduled alarms.
+ */
+ @GuardedBy("mUserWakeupLock")
+ private final SparseLongArray mUserStarts = new SparseLongArray();
+ /**
+ * A list of users that are in a phase after they have been started but before alarms were
+ * initialized.
+ */
+ @GuardedBy("mUserWakeupLock")
+ private final SparseLongArray mStartingUsers = new SparseLongArray();
+ private Executor mBackgroundExecutor;
+ private static final File USER_WAKEUP_DIR = new File(Environment.getDataSystemDirectory(),
+ ROOT_DIR_NAME);
+ private static final Random sRandom = new Random(500);
+
+ /**
+ * Initialize mUserWakeups with persisted values.
+ */
+ public void init() {
+ mBackgroundExecutor = BackgroundThread.getExecutor();
+ mBackgroundExecutor.execute(this::readUserIdList);
+ }
+
+ /**
+ * Add user wakeup for the alarm.
+ * @param userId Id of the user that scheduled alarm.
+ * @param alarmTime time when alarm is expected to trigger.
+ */
+ public void addUserWakeup(int userId, long alarmTime) {
+ synchronized (mUserWakeupLock) {
+ // This should not be needed, but if an app in the user is scheduling an alarm clock, we
+ // consider the user start complete. There is a dedicated removal when user is started.
+ mStartingUsers.delete(userId);
+ mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
+ }
+ updateUserListFile();
+ }
+
+ /**
+ * Remove wakeup scheduled for the user with given userId if present.
+ */
+ public void removeUserWakeup(int userId) {
+ synchronized (mUserWakeupLock) {
+ mUserStarts.delete(userId);
+ }
+ updateUserListFile();
+ }
+
+ /**
+ * Get ids of users that need to be started now.
+ * @param nowElapsed current time.
+ * @return user ids to be started, or empty if no user needs to be started.
+ */
+ public int[] getUserIdsToWakeup(long nowElapsed) {
+ synchronized (mUserWakeupLock) {
+ final int[] userIds = new int[mUserStarts.size()];
+ int index = 0;
+ for (int i = mUserStarts.size() - 1; i >= 0; i--) {
+ if (mUserStarts.valueAt(i) <= nowElapsed) {
+ userIds[index++] = mUserStarts.keyAt(i);
+ }
+ }
+ return Arrays.copyOfRange(userIds, 0, index);
+ }
+ }
+
+ /**
+ * Persist user ids that have alarms scheduled so that they can be started after device reboot.
+ */
+ private void updateUserListFile() {
+ mBackgroundExecutor.execute(() -> {
+ try {
+ writeUserIdList();
+ if (DEBUG) {
+ synchronized (mUserWakeupLock) {
+ Slog.i(USER_WAKEUP_TAG, "Printing out user wakeups " + mUserStarts.size());
+ for (int i = 0; i < mUserStarts.size(); i++) {
+ Slog.i(USER_WAKEUP_TAG, "User id: " + mUserStarts.keyAt(i) + " time: "
+ + mUserStarts.valueAt(i));
+ }
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(USER_WAKEUP_TAG, "Failed to write " + e.getLocalizedMessage());
+ }
+ });
+ }
+
+ /**
+ * Return scheduled start time for user or -1 if user does not have alarm set.
+ */
+ @VisibleForTesting
+ long getWakeupTimeForUserForTest(int userId) {
+ synchronized (mUserWakeupLock) {
+ return mUserStarts.get(userId, -1);
+ }
+ }
+
+ /**
+ * Move user from wakeup list to starting user list.
+ */
+ public void onUserStarting(int userId) {
+ synchronized (mUserWakeupLock) {
+ mStartingUsers.put(userId, getWakeupTimeForUserForTest(userId));
+ mUserStarts.delete(userId);
+ }
+ }
+
+ /**
+ * Remove userId from starting user list once start is complete.
+ */
+ public void onUserStarted(int userId) {
+ synchronized (mUserWakeupLock) {
+ mStartingUsers.delete(userId);
+ }
+ updateUserListFile();
+ }
+
+ /**
+ * Remove userId from the store when the user is removed.
+ */
+ public void onUserRemoved(int userId) {
+ synchronized (mUserWakeupLock) {
+ mUserStarts.delete(userId);
+ mStartingUsers.delete(userId);
+ }
+ updateUserListFile();
+ }
+
+ /**
+ * Get the soonest wakeup time in the store.
+ */
+ public long getNextWakeupTime() {
+ long nextWakeupTime = -1;
+ synchronized (mUserWakeupLock) {
+ for (int i = 0; i < mUserStarts.size(); i++) {
+ if (mUserStarts.valueAt(i) < nextWakeupTime || nextWakeupTime == -1) {
+ nextWakeupTime = mUserStarts.valueAt(i);
+ }
+ }
+ }
+ return nextWakeupTime;
+ }
+
+ private static long getUserWakeupOffset() {
+ return sRandom.nextLong(USER_START_TIME_DEVIATION_LIMIT_MS * 2)
+ - USER_START_TIME_DEVIATION_LIMIT_MS;
+ }
+
+ /**
+ * Write a list of ids for users who have alarm scheduled.
+ * Sample XML file:
+ *
+ * <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ * <users version="1">
+ * <user user_id="12" />
+ * <user user_id="10" />
+ * </users>
+ * ~
+ */
+ private void writeUserIdList() {
+ final AtomicFile file = getUserWakeupFile();
+ if (file == null) {
+ return;
+ }
+ try (FileOutputStream fos = file.startWrite(SystemClock.uptimeMillis())) {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_USERS);
+ XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
+ final List<Pair<Integer, Long>> listOfUsers = new ArrayList<>();
+ synchronized (mUserWakeupLock) {
+ for (int i = 0; i < mUserStarts.size(); i++) {
+ listOfUsers.add(new Pair<>(mUserStarts.keyAt(i), mUserStarts.valueAt(i)));
+ }
+ for (int i = 0; i < mStartingUsers.size(); i++) {
+ listOfUsers.add(new Pair<>(mStartingUsers.keyAt(i), mStartingUsers.valueAt(i)));
+ }
+ }
+ Collections.sort(listOfUsers, Comparator.comparingLong(pair -> pair.second));
+ for (int i = 0; i < listOfUsers.size(); i++) {
+ out.startTag(null, TAG_USER);
+ XmlUtils.writeIntAttribute(out, ATTR_USER_ID, listOfUsers.get(i).first);
+ out.endTag(null, TAG_USER);
+ }
+ out.endTag(null, TAG_USERS);
+ out.endDocument();
+ file.finishWrite(fos);
+ } catch (IOException e) {
+ Slog.wtf(USER_WAKEUP_TAG, "Error writing user wakeup data", e);
+ file.delete();
+ }
+ }
+
+ private void readUserIdList() {
+ final AtomicFile userWakeupFile = getUserWakeupFile();
+ if (userWakeupFile == null) {
+ return;
+ } else if (!userWakeupFile.exists()) {
+ Slog.w(USER_WAKEUP_TAG, "User wakeup file not available: "
+ + userWakeupFile.getBaseFile());
+ return;
+ }
+ synchronized (mUserWakeupLock) {
+ mUserStarts.clear();
+ mStartingUsers.clear();
+ }
+ try (FileInputStream fis = userWakeupFile.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Skip
+ }
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(USER_WAKEUP_TAG, "Unable to read user list. No start tag found in "
+ + userWakeupFile.getBaseFile());
+ return;
+ }
+ int version = -1;
+ if (parser.getName().equals(TAG_USERS)) {
+ version = parser.getAttributeInt(null, ATTR_VERSION, version);
+ }
+
+ long counter = 0;
+ final long currentTime = SystemClock.elapsedRealtime();
+ // Time delay between now and first user wakeup is scheduled.
+ final long scheduleOffset = currentTime + BUFFER_TIME_MS + getUserWakeupOffset();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(TAG_USER)) {
+ final int id = parser.getAttributeInt(null, ATTR_USER_ID);
+ synchronized (mUserWakeupLock) {
+ mUserStarts.put(id, scheduleOffset + (counter++
+ * INITIAL_USER_START_SCHEDULING_DELAY_MS));
+ }
+ }
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(USER_WAKEUP_TAG, "Error reading user wakeup data", e);
+ }
+ }
+
+ @Nullable
+ private AtomicFile getUserWakeupFile() {
+ if (!USER_WAKEUP_DIR.exists() && !USER_WAKEUP_DIR.mkdir()) {
+ Slog.wtf(USER_WAKEUP_TAG, "Failed to mkdir() user list file: " + USER_WAKEUP_DIR);
+ return null;
+ }
+ final File userFile = new File(USER_WAKEUP_DIR, USERS_FILE_NAME);
+ return new AtomicFile(userFile);
+ }
+
+ void dump(IndentingPrintWriter pw, long nowELAPSED) {
+ synchronized (mUserWakeupLock) {
+ pw.increaseIndent();
+ pw.print("User wakeup store file path: ");
+ final AtomicFile file = getUserWakeupFile();
+ if (file == null) {
+ pw.println("null");
+ } else {
+ pw.println(file.getBaseFile().getAbsolutePath());
+ }
+ pw.println(mUserStarts.size() + " user wakeups scheduled: ");
+ for (int i = 0; i < mUserStarts.size(); i++) {
+ pw.print("UserId: ");
+ pw.print(mUserStarts.keyAt(i));
+ pw.print(", userStartTime: ");
+ TimeUtils.formatDuration(mUserStarts.valueAt(i), nowELAPSED, pw);
+ pw.println();
+ }
+ pw.println(mStartingUsers.size() + " starting users: ");
+ for (int i = 0; i < mStartingUsers.size(); i++) {
+ pw.print("UserId: ");
+ pw.print(mStartingUsers.keyAt(i));
+ pw.print(", userStartTime: ");
+ TimeUtils.formatDuration(mStartingUsers.valueAt(i), nowELAPSED, pw);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
+}
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 bd00c03..88a3c6f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -304,6 +304,8 @@
private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
+ /** Need direct access to this for testing. */
+ private final FlexibilityController mFlexibilityController;
/** Needed to get next estimated launch time. */
private final PrefetchController mPrefetchController;
/** Needed to get remaining quota time. */
@@ -2701,17 +2703,16 @@
mControllers = new ArrayList<StateController>();
mPrefetchController = new PrefetchController(this);
mControllers.add(mPrefetchController);
- final FlexibilityController flexibilityController =
- new FlexibilityController(this, mPrefetchController);
- mControllers.add(flexibilityController);
+ mFlexibilityController = new FlexibilityController(this, mPrefetchController);
+ mControllers.add(mFlexibilityController);
mConnectivityController =
- new ConnectivityController(this, flexibilityController);
+ new ConnectivityController(this, mFlexibilityController);
mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
- final IdleController idleController = new IdleController(this, flexibilityController);
+ final IdleController idleController = new IdleController(this, mFlexibilityController);
mControllers.add(idleController);
final BatteryController batteryController =
- new BatteryController(this, flexibilityController);
+ new BatteryController(this, mFlexibilityController);
mControllers.add(batteryController);
mStorageController = new StorageController(this);
mControllers.add(mStorageController);
@@ -4783,6 +4784,10 @@
* Returns whether the app holds the {@link Manifest.permission.RUN_BACKUP_JOBS} permission.
*/
private boolean hasRunBackupJobsPermission(@NonNull String packageName, int packageUid) {
+ // This permission is currently hidden so always return false for now (see b/331272951)
+ return false;
+
+ /**
if (packageName == null) {
Slog.wtfStack(TAG,
"Expected a non-null package name when calling hasRunBackupJobsPermission");
@@ -4793,6 +4798,7 @@
android.Manifest.permission.RUN_BACKUP_JOBS,
PermissionChecker.PID_UNKNOWN, packageUid, packageName)
== PermissionChecker.PERMISSION_GRANTED;
+ */
}
/**
@@ -5556,6 +5562,15 @@
return 0;
}
+ // Shell command infrastructure: set flex policy
+ void setFlexPolicy(boolean override, int appliedConstraints) {
+ if (DEBUG) {
+ Slog.v(TAG, "setFlexPolicy(): " + override + "/" + appliedConstraints);
+ }
+
+ mFlexibilityController.setLocalPolicyForTesting(override, appliedConstraints);
+ }
+
void setMonitorBattery(boolean enabled) {
synchronized (mLock) {
mBatteryStateTracker.setMonitorBatteryLocked(enabled);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index af7b27e..ac240cc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -27,6 +27,7 @@
import android.os.UserHandle;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.job.controllers.JobStatus;
import java.io.PrintWriter;
@@ -59,6 +60,10 @@
return cancelJob(pw);
case "monitor-battery":
return monitorBattery(pw);
+ case "disable-flex-policy":
+ return disableFlexPolicy(pw);
+ case "enable-flex-policy":
+ return enableFlexPolicy(pw);
case "get-aconfig-flag-state":
return getAconfigFlagState(pw);
case "get-battery-seq":
@@ -91,6 +96,8 @@
return resetExecutionQuota(pw);
case "reset-schedule-quota":
return resetScheduleQuota(pw);
+ case "reset-flex-policy":
+ return resetFlexPolicy(pw);
case "stop":
return stop(pw);
case "trigger-dock-state":
@@ -346,6 +353,65 @@
return 0;
}
+ private int disableFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("disable flex policy");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(true, 0);
+ pw.println("Set flex policy to 0");
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private int enableFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("enable flex policy");
+
+ int enabled = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-o":
+ case "--option":
+ final String constraint = getNextArgRequired();
+ switch (constraint) {
+ case "battery-not-low":
+ enabled |= JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
+ break;
+ case "charging":
+ enabled |= JobStatus.CONSTRAINT_CHARGING;
+ break;
+ case "connectivity":
+ enabled |= JobStatus.CONSTRAINT_CONNECTIVITY;
+ break;
+ case "idle":
+ enabled |= JobStatus.CONSTRAINT_IDLE;
+ break;
+ default:
+ pw.println("Unsupported option: " + constraint);
+ return -1;
+ }
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(true, enabled);
+ pw.println("Set flex policy to " + enabled);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int getAconfigFlagState(PrintWriter pw) throws Exception {
checkPermission("get aconfig flag state", Manifest.permission.DUMP);
@@ -581,6 +647,19 @@
return 0;
}
+ private int resetFlexPolicy(PrintWriter pw) throws Exception {
+ checkPermission("reset flex policy");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.setFlexPolicy(false, 0);
+ pw.println("Reset flex policy to its default state");
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int resetExecutionQuota(PrintWriter pw) throws Exception {
checkPermission("reset execution quota");
@@ -773,6 +852,15 @@
pw.println(" monitor-battery [on|off]");
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
pw.println(" on makes get-battery-seq useful.");
+ pw.println(" enable-flex-policy --option <option>");
+ pw.println(" Enable flex policy with the specified options. Supported options are");
+ pw.println(" battery-not-low, charging, connectivity, idle.");
+ pw.println(" Multiple enable options can be specified (e.g.");
+ pw.println(" enable-flex-policy --option battery-not-low --option charging");
+ pw.println(" disable-flex-policy");
+ pw.println(" Turn off flex policy so that it does not affect job execution.");
+ pw.println(" reset-flex-policy");
+ pw.println(" Resets the flex policy to its default state.");
pw.println(" get-aconfig-flag-state FULL_FLAG_NAME");
pw.println(" Return the state of the specified aconfig flag, if known. The flag name");
pw.println(" must be fully qualified.");
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 ee9400f..852b00b 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
@@ -328,6 +328,9 @@
@GuardedBy("mLock")
private final ArraySet<String> mPackagesToCheck = new ArraySet<>();
+ @GuardedBy("mLock")
+ private boolean mLocalOverride;
+
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
super(service);
@@ -1923,6 +1926,27 @@
}
}
+ /**
+ * If {@code override} is true, uses {@code appliedConstraints} for flex policy evaluation,
+ * overriding anything else that was set. If {@code override} is false, any previous calls
+ * will be discarded and the policy will be reset to the normal default policy.
+ */
+ public void setLocalPolicyForTesting(boolean override, int appliedConstraints) {
+ synchronized (mLock) {
+ final boolean recheckJobs = mLocalOverride != override
+ || mAppliedConstraints != appliedConstraints;
+ mLocalOverride = override;
+ if (mLocalOverride) {
+ mAppliedConstraints = appliedConstraints;
+ } else {
+ mAppliedConstraints = mFcConfig.APPLIED_CONSTRAINTS;
+ }
+ if (recheckJobs) {
+ mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
+ }
+ }
+ }
+
@Override
@GuardedBy("mLock")
public void dumpConstants(IndentingPrintWriter pw) {
@@ -1932,6 +1956,12 @@
@Override
@GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+ if (mLocalOverride) {
+ pw.println("Local override active");
+ }
+ pw.print("Applied Flexible Constraints:");
+ JobStatus.dumpConstraints(pw, mAppliedConstraints);
+ pw.println();
pw.print("Satisfied Flexible Constraints:");
JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints);
pw.println();
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 edd86e3..d643768 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
@@ -1213,7 +1213,8 @@
return ACTIVE_INDEX;
}
- final boolean isEligibleAsBackupJob = job.getTriggerContentUris() != null
+ final boolean isEligibleAsBackupJob = false // this exemption is being disabled for now.
+ && job.getTriggerContentUris() != null
&& job.getRequiredNetwork() != null
&& !job.hasLateConstraint()
&& mJobSchedulerInternal.hasRunBackupJobsPermission(sourcePackageName, sourceUid);
diff --git a/core/api/current.txt b/core/api/current.txt
index 51a71d2..b19c3ab 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -285,7 +285,6 @@
field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
- field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -5464,15 +5463,11 @@
method public int getDeferralPolicy();
method @Nullable public String getDeliveryGroupMatchingKey();
method public int getDeliveryGroupPolicy();
- method @FlaggedApi("android.app.bcast_event_timestamps") public long getEventTriggerTimestampMillis();
- method @FlaggedApi("android.app.bcast_event_timestamps") public long getRemoteEventTriggerTimestampMillis();
method public boolean isShareIdentityEnabled();
method @NonNull public static android.app.BroadcastOptions makeBasic();
method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
- method @FlaggedApi("android.app.bcast_event_timestamps") public void setEventTriggerTimestampMillis(long);
- method @FlaggedApi("android.app.bcast_event_timestamps") public void setRemoteEventTriggerTimestampMillis(long);
method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
method @NonNull public android.os.Bundle toBundle();
field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0
@@ -9247,140 +9242,140 @@
package android.app.slice {
- public final class Slice implements android.os.Parcelable {
- ctor protected Slice(android.os.Parcel);
- method public int describeContents();
- method public java.util.List<java.lang.String> getHints();
- method public java.util.List<android.app.slice.SliceItem> getItems();
- method @Nullable public android.app.slice.SliceSpec getSpec();
- method public android.net.Uri getUri();
- method public boolean isCallerNeeded();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
- field public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
- field public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
- field public static final String HINT_ACTIONS = "actions";
- field public static final String HINT_ERROR = "error";
- field public static final String HINT_HORIZONTAL = "horizontal";
- field public static final String HINT_KEYWORDS = "keywords";
- field public static final String HINT_LARGE = "large";
- field public static final String HINT_LAST_UPDATED = "last_updated";
- field public static final String HINT_LIST = "list";
- field public static final String HINT_LIST_ITEM = "list_item";
- field public static final String HINT_NO_TINT = "no_tint";
- field public static final String HINT_PARTIAL = "partial";
- field public static final String HINT_PERMISSION_REQUEST = "permission_request";
- field public static final String HINT_SEE_MORE = "see_more";
- field public static final String HINT_SELECTED = "selected";
- field public static final String HINT_SHORTCUT = "shortcut";
- field public static final String HINT_SUMMARY = "summary";
- field public static final String HINT_TITLE = "title";
- field public static final String HINT_TTL = "ttl";
- field public static final String SUBTYPE_COLOR = "color";
- field public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
- field public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
- field public static final String SUBTYPE_MAX = "max";
- field public static final String SUBTYPE_MESSAGE = "message";
- field public static final String SUBTYPE_MILLIS = "millis";
- field public static final String SUBTYPE_PRIORITY = "priority";
- field public static final String SUBTYPE_RANGE = "range";
- field public static final String SUBTYPE_SOURCE = "source";
- field public static final String SUBTYPE_TOGGLE = "toggle";
- field public static final String SUBTYPE_VALUE = "value";
+ @Deprecated public final class Slice implements android.os.Parcelable {
+ ctor @Deprecated protected Slice(android.os.Parcel);
+ method @Deprecated public int describeContents();
+ method @Deprecated public java.util.List<java.lang.String> getHints();
+ method @Deprecated public java.util.List<android.app.slice.SliceItem> getItems();
+ method @Deprecated @Nullable public android.app.slice.SliceSpec getSpec();
+ method @Deprecated public android.net.Uri getUri();
+ method @Deprecated public boolean isCallerNeeded();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+ field @Deprecated public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
+ field @Deprecated public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+ field @Deprecated public static final String HINT_ACTIONS = "actions";
+ field @Deprecated public static final String HINT_ERROR = "error";
+ field @Deprecated public static final String HINT_HORIZONTAL = "horizontal";
+ field @Deprecated public static final String HINT_KEYWORDS = "keywords";
+ field @Deprecated public static final String HINT_LARGE = "large";
+ field @Deprecated public static final String HINT_LAST_UPDATED = "last_updated";
+ field @Deprecated public static final String HINT_LIST = "list";
+ field @Deprecated public static final String HINT_LIST_ITEM = "list_item";
+ field @Deprecated public static final String HINT_NO_TINT = "no_tint";
+ field @Deprecated public static final String HINT_PARTIAL = "partial";
+ field @Deprecated public static final String HINT_PERMISSION_REQUEST = "permission_request";
+ field @Deprecated public static final String HINT_SEE_MORE = "see_more";
+ field @Deprecated public static final String HINT_SELECTED = "selected";
+ field @Deprecated public static final String HINT_SHORTCUT = "shortcut";
+ field @Deprecated public static final String HINT_SUMMARY = "summary";
+ field @Deprecated public static final String HINT_TITLE = "title";
+ field @Deprecated public static final String HINT_TTL = "ttl";
+ field @Deprecated public static final String SUBTYPE_COLOR = "color";
+ field @Deprecated public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
+ field @Deprecated public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
+ field @Deprecated public static final String SUBTYPE_MAX = "max";
+ field @Deprecated public static final String SUBTYPE_MESSAGE = "message";
+ field @Deprecated public static final String SUBTYPE_MILLIS = "millis";
+ field @Deprecated public static final String SUBTYPE_PRIORITY = "priority";
+ field @Deprecated public static final String SUBTYPE_RANGE = "range";
+ field @Deprecated public static final String SUBTYPE_SOURCE = "source";
+ field @Deprecated public static final String SUBTYPE_TOGGLE = "toggle";
+ field @Deprecated public static final String SUBTYPE_VALUE = "value";
}
- public static class Slice.Builder {
- ctor public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec);
- ctor public Slice.Builder(@NonNull android.app.slice.Slice.Builder);
- method public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String);
- method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String);
- method public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>);
- method public android.app.slice.Slice build();
- method public android.app.slice.Slice.Builder setCallerNeeded(boolean);
+ @Deprecated public static class Slice.Builder {
+ ctor @Deprecated public Slice.Builder(@NonNull android.net.Uri, android.app.slice.SliceSpec);
+ ctor @Deprecated public Slice.Builder(@NonNull android.app.slice.Slice.Builder);
+ method @Deprecated public android.app.slice.Slice.Builder addAction(@NonNull android.app.PendingIntent, @NonNull android.app.slice.Slice, @Nullable String);
+ method @Deprecated public android.app.slice.Slice.Builder addBundle(android.os.Bundle, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addInt(int, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addLong(long, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice.Builder addSubSlice(@NonNull android.app.slice.Slice, @Nullable String);
+ method @Deprecated public android.app.slice.Slice.Builder addText(CharSequence, @Nullable String, java.util.List<java.lang.String>);
+ method @Deprecated public android.app.slice.Slice build();
+ method @Deprecated public android.app.slice.Slice.Builder setCallerNeeded(boolean);
}
- public final class SliceItem implements android.os.Parcelable {
- method public int describeContents();
- method public android.app.PendingIntent getAction();
- method public android.os.Bundle getBundle();
- method public String getFormat();
- method @NonNull public java.util.List<java.lang.String> getHints();
- method public android.graphics.drawable.Icon getIcon();
- method public int getInt();
- method public long getLong();
- method public android.app.RemoteInput getRemoteInput();
- method public android.app.slice.Slice getSlice();
- method public String getSubType();
- method public CharSequence getText();
- method public boolean hasHint(String);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
- field public static final String FORMAT_ACTION = "action";
- field public static final String FORMAT_BUNDLE = "bundle";
- field public static final String FORMAT_IMAGE = "image";
- field public static final String FORMAT_INT = "int";
- field public static final String FORMAT_LONG = "long";
- field public static final String FORMAT_REMOTE_INPUT = "input";
- field public static final String FORMAT_SLICE = "slice";
- field public static final String FORMAT_TEXT = "text";
+ @Deprecated public final class SliceItem implements android.os.Parcelable {
+ method @Deprecated public int describeContents();
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public android.os.Bundle getBundle();
+ method @Deprecated public String getFormat();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getHints();
+ method @Deprecated public android.graphics.drawable.Icon getIcon();
+ method @Deprecated public int getInt();
+ method @Deprecated public long getLong();
+ method @Deprecated public android.app.RemoteInput getRemoteInput();
+ method @Deprecated public android.app.slice.Slice getSlice();
+ method @Deprecated public String getSubType();
+ method @Deprecated public CharSequence getText();
+ method @Deprecated public boolean hasHint(String);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
+ field @Deprecated public static final String FORMAT_ACTION = "action";
+ field @Deprecated public static final String FORMAT_BUNDLE = "bundle";
+ field @Deprecated public static final String FORMAT_IMAGE = "image";
+ field @Deprecated public static final String FORMAT_INT = "int";
+ field @Deprecated public static final String FORMAT_LONG = "long";
+ field @Deprecated public static final String FORMAT_REMOTE_INPUT = "input";
+ field @Deprecated public static final String FORMAT_SLICE = "slice";
+ field @Deprecated public static final String FORMAT_TEXT = "text";
}
- public class SliceManager {
- method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method public int checkSlicePermission(@NonNull android.net.Uri, int, int);
- method @NonNull public java.util.List<android.net.Uri> getPinnedSlices();
- method @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
- method @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri);
- method public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri);
- method @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent);
- method public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
- method public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri);
- method public void unpinSlice(@NonNull android.net.Uri);
- field public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
- field public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+ @Deprecated public class SliceManager {
+ method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated @Nullable public android.app.slice.Slice bindSlice(@NonNull android.content.Intent, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated public int checkSlicePermission(@NonNull android.net.Uri, int, int);
+ method @Deprecated @NonNull public java.util.List<android.net.Uri> getPinnedSlices();
+ method @Deprecated @NonNull public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
+ method @Deprecated @NonNull @WorkerThread public java.util.Collection<android.net.Uri> getSliceDescendants(@NonNull android.net.Uri);
+ method @Deprecated public void grantSlicePermission(@NonNull String, @NonNull android.net.Uri);
+ method @Deprecated @Nullable public android.net.Uri mapIntentToUri(@NonNull android.content.Intent);
+ method @Deprecated public void pinSlice(@NonNull android.net.Uri, @NonNull java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated public void revokeSlicePermission(@NonNull String, @NonNull android.net.Uri);
+ method @Deprecated public void unpinSlice(@NonNull android.net.Uri);
+ field @Deprecated public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
+ field @Deprecated public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
}
- public class SliceMetrics {
- ctor public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public void logHidden();
- method public void logTouch(int, @NonNull android.net.Uri);
- method public void logVisible();
+ @Deprecated public class SliceMetrics {
+ ctor @Deprecated public SliceMetrics(@NonNull android.content.Context, @NonNull android.net.Uri);
+ method @Deprecated public void logHidden();
+ method @Deprecated public void logTouch(int, @NonNull android.net.Uri);
+ method @Deprecated public void logVisible();
}
- public abstract class SliceProvider extends android.content.ContentProvider {
- ctor public SliceProvider(@NonNull java.lang.String...);
- ctor public SliceProvider();
- method public final int delete(android.net.Uri, String, String[]);
- method public final String getType(android.net.Uri);
- method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
- method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
- method @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
- method @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri);
- method @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent);
- method public void onSlicePinned(android.net.Uri);
- method public void onSliceUnpinned(android.net.Uri);
- method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String);
- method public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal);
- method public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal);
- method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
- field public static final String SLICE_TYPE = "vnd.android.slice";
+ @Deprecated public abstract class SliceProvider extends android.content.ContentProvider {
+ ctor @Deprecated public SliceProvider(@NonNull java.lang.String...);
+ ctor @Deprecated public SliceProvider();
+ method @Deprecated public final int delete(android.net.Uri, String, String[]);
+ method @Deprecated public final String getType(android.net.Uri);
+ method @Deprecated public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+ method @Deprecated public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+ method @Deprecated @NonNull public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
+ method @Deprecated @NonNull public java.util.Collection<android.net.Uri> onGetSliceDescendants(@NonNull android.net.Uri);
+ method @Deprecated @NonNull public android.net.Uri onMapIntentToUri(android.content.Intent);
+ method @Deprecated public void onSlicePinned(android.net.Uri);
+ method @Deprecated public void onSliceUnpinned(android.net.Uri);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], String, String[], String, android.os.CancellationSignal);
+ method @Deprecated public final android.database.Cursor query(android.net.Uri, String[], android.os.Bundle, android.os.CancellationSignal);
+ method @Deprecated public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
+ field @Deprecated public static final String SLICE_TYPE = "vnd.android.slice";
}
- public final class SliceSpec implements android.os.Parcelable {
- ctor public SliceSpec(@NonNull String, int);
- method public boolean canRender(@NonNull android.app.slice.SliceSpec);
- method public int describeContents();
- method public int getRevision();
- method public String getType();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
+ @Deprecated public final class SliceSpec implements android.os.Parcelable {
+ ctor @Deprecated public SliceSpec(@NonNull String, int);
+ method @Deprecated public boolean canRender(@NonNull android.app.slice.SliceSpec);
+ method @Deprecated public int describeContents();
+ method @Deprecated public int getRevision();
+ method @Deprecated public String getType();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.app.slice.SliceSpec> CREATOR;
}
}
@@ -10152,17 +10147,17 @@
public interface ComponentCallbacks {
method public void onConfigurationChanged(@NonNull android.content.res.Configuration);
- method public void onLowMemory();
+ method @Deprecated public void onLowMemory();
}
public interface ComponentCallbacks2 extends android.content.ComponentCallbacks {
method public void onTrimMemory(int);
field public static final int TRIM_MEMORY_BACKGROUND = 40; // 0x28
- field public static final int TRIM_MEMORY_COMPLETE = 80; // 0x50
- field public static final int TRIM_MEMORY_MODERATE = 60; // 0x3c
- field public static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; // 0xf
- field public static final int TRIM_MEMORY_RUNNING_LOW = 10; // 0xa
- field public static final int TRIM_MEMORY_RUNNING_MODERATE = 5; // 0x5
+ field @Deprecated public static final int TRIM_MEMORY_COMPLETE = 80; // 0x50
+ field @Deprecated public static final int TRIM_MEMORY_MODERATE = 60; // 0x3c
+ field @Deprecated public static final int TRIM_MEMORY_RUNNING_CRITICAL = 15; // 0xf
+ field @Deprecated public static final int TRIM_MEMORY_RUNNING_LOW = 10; // 0xa
+ field @Deprecated public static final int TRIM_MEMORY_RUNNING_MODERATE = 5; // 0x5
field public static final int TRIM_MEMORY_UI_HIDDEN = 20; // 0x14
}
@@ -37342,7 +37337,6 @@
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL";
- field @FlaggedApi("android.provider.backup_tasks_settings_screen") public static final String ACTION_REQUEST_RUN_BACKUP_JOBS = "android.settings.REQUEST_RUN_BACKUP_JOBS";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String ACTION_SATELLITE_SETTING = "android.settings.SATELLITE_SETTING";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 702a47e..501203e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2178,8 +2178,15 @@
package android.app.contextualsearch {
- @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager {
- method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
+ ctor public CallbackToken();
+ method public int describeContents();
+ method public void getContextualSearchState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR;
+ }
+
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int);
field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH";
field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ca70f03..6189703 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -803,6 +803,14 @@
}
+package android.app.contextualsearch {
+
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable {
+ method @NonNull public android.os.IBinder getToken();
+ }
+
+}
+
package android.app.job {
public class JobParameters implements android.os.Parcelable {
@@ -1519,6 +1527,13 @@
package android.hardware {
+ @Deprecated public class Camera {
+ method @Deprecated public static void getCameraInfo(int, @NonNull android.content.Context, boolean, android.hardware.Camera.CameraInfo);
+ method @Deprecated public static int getNumberOfCameras(@NonNull android.content.Context);
+ method @Deprecated public static android.hardware.Camera open(int, @NonNull android.content.Context, boolean);
+ method @Deprecated public final void setPreviewSurface(android.view.Surface) throws java.io.IOException;
+ }
+
public final class SensorPrivacyManager {
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
@@ -1551,7 +1566,7 @@
public static class BiometricPrompt.Builder {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
- method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
+ method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowedSensorIds(@NonNull java.util.List<java.lang.Integer>);
}
@@ -3108,6 +3123,14 @@
}
+package android.service.ondeviceintelligence {
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
+ method public void onReady();
+ }
+
+}
+
package android.service.quickaccesswallet {
public interface QuickAccessWalletClient extends java.io.Closeable {
@@ -3653,6 +3676,10 @@
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
}
+ public final class PointerIcon implements android.os.Parcelable {
+ method @FlaggedApi("android.view.flags.enable_vector_cursors") public void setDrawNativeDropShadow(boolean);
+ }
+
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod {
method public abstract String asyncImpl() default "";
}
@@ -3954,6 +3981,7 @@
public final class InputMethodManager {
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
+ method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void finishTrackingPendingImeVisibilityRequests();
method public int getDisplayId();
method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
method @FlaggedApi("android.view.inputmethod.imm_userhandle_hostsidetests") @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
diff --git a/core/java/android/accessibilityservice/BrailleDisplayController.java b/core/java/android/accessibilityservice/BrailleDisplayController.java
index 5282aa3..7334676 100644
--- a/core/java/android/accessibilityservice/BrailleDisplayController.java
+++ b/core/java/android/accessibilityservice/BrailleDisplayController.java
@@ -305,4 +305,6 @@
@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
@TestApi
String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
+ /** @hide */
+ String TEST_BRAILLE_DISPLAY_NAME = "NAME";
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0c54351..7725561 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5672,9 +5672,11 @@
}
/**
- * Return if a given profile is in the foreground.
+ * Returns whether the given user, or its parent (if the user is a profile), is in the
+ * foreground.
* @param userHandle UserHandle to check
- * @return Returns the boolean result.
+ * @return whether the user is the foreground user or, if it is a profile, whether its parent
+ * is the foreground user
* @hide
*/
@RequiresPermission(anyOf = {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5843e51..e66f7fe 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -148,6 +148,13 @@
public abstract void onUserRemoved(@UserIdInt int userId);
/**
+ * Start user, if it is not already running, but don't bring it to foreground.
+ * @param userId ID of the user to start
+ * @return true if the user has been successfully started
+ */
+ public abstract boolean startUserInBackground(int userId);
+
+ /**
* Kill foreground apps from the specified user.
*/
public abstract void killForegroundAppsForUser(@UserIdInt int userId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4bf8879..8913d6d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -365,7 +365,7 @@
@UnsupportedAppUsage
private ContextImpl mSystemContext;
@GuardedBy("this")
- private SparseArray<ContextImpl> mDisplaySystemUiContexts;
+ private ArrayList<WeakReference<ContextImpl>> mDisplaySystemUiContexts;
@UnsupportedAppUsage
static volatile IPackageManager sPackageManager;
@@ -3033,14 +3033,19 @@
public ContextImpl getSystemUiContext(int displayId) {
synchronized (this) {
if (mDisplaySystemUiContexts == null) {
- mDisplaySystemUiContexts = new SparseArray<>();
+ mDisplaySystemUiContexts = new ArrayList<>();
}
- ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId);
- if (systemUiContext == null) {
- systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
- mDisplaySystemUiContexts.put(displayId, systemUiContext);
+
+ mDisplaySystemUiContexts.removeIf(contextRef -> contextRef.refersTo(null));
+
+ ContextImpl context = getSystemUiContextNoCreateLocked(displayId);
+ if (context != null) {
+ return context;
}
- return systemUiContext;
+
+ context = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
+ mDisplaySystemUiContexts.add(new WeakReference<>(context));
+ return context;
}
}
@@ -3048,18 +3053,30 @@
@Override
public ContextImpl getSystemUiContextNoCreate() {
synchronized (this) {
- if (mDisplaySystemUiContexts == null) return null;
- return mDisplaySystemUiContexts.get(DEFAULT_DISPLAY);
+ if (mDisplaySystemUiContexts == null) {
+ return null;
+ }
+ return getSystemUiContextNoCreateLocked(DEFAULT_DISPLAY);
}
}
+ @GuardedBy("this")
+ @Nullable
+ private ContextImpl getSystemUiContextNoCreateLocked(int displayId) {
+ for (int i = 0; i < mDisplaySystemUiContexts.size(); i++) {
+ ContextImpl context = mDisplaySystemUiContexts.get(i).get();
+ if (context != null && context.getDisplayId() == displayId) {
+ return context;
+ }
+ }
+ return null;
+ }
+
void onSystemUiContextCleanup(ContextImpl context) {
synchronized (this) {
if (mDisplaySystemUiContexts == null) return;
- final int index = mDisplaySystemUiContexts.indexOfValue(context);
- if (index >= 0) {
- mDisplaySystemUiContexts.removeAt(index);
- }
+ mDisplaySystemUiContexts.removeIf(
+ contextRef -> contextRef.refersTo(null) || contextRef.refersTo(context));
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0ed25eb..7ed10e7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,7 +18,7 @@
import static android.location.flags.Flags.FLAG_LOCATION_BYPASS;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER;
import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED;
import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED;
@@ -3246,7 +3246,7 @@
* @hide
*/
public static @Mode int opToDefaultMode(int op) {
- if (op == OP_TAKE_AUDIO_FOCUS && foregroundAudioControl()) {
+ if (op == OP_TAKE_AUDIO_FOCUS && roForegroundAudioControl()) {
// when removing the flag, change the entry in sAppOpInfos for OP_TAKE_AUDIO_FOCUS
return AppOpsManager.MODE_FOREGROUND;
}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 60d622d..4db3727 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -18,8 +18,6 @@
import static android.app.ActivityOptions.BackgroundActivityStartMode;
-import android.annotation.CurrentTimeMillisLong;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -69,8 +67,6 @@
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
private @DeferralPolicy int mDeferralPolicy;
- private @CurrentTimeMillisLong long mEventTriggerTimestampMillis;
- private @CurrentTimeMillisLong long mRemoteEventTriggerTimestampMillis;
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -196,18 +192,6 @@
"android:broadcast.idForResponseEvent";
/**
- * Corresponds to {@link #setEventTriggerTimestampMillis(long)}.
- */
- private static final String KEY_EVENT_TRIGGER_TIMESTAMP =
- "android:broadcast.eventTriggerTimestamp";
-
- /**
- * Corresponds to {@link #setRemoteEventTriggerTimestampMillis(long)}.
- */
- private static final String KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP =
- "android:broadcast.remoteEventTriggerTimestamp";
-
- /**
* Corresponds to {@link #setDeliveryGroupPolicy(int)}.
*/
private static final String KEY_DELIVERY_GROUP_POLICY =
@@ -359,8 +343,6 @@
mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
- mEventTriggerTimestampMillis = opts.getLong(KEY_EVENT_TRIGGER_TIMESTAMP);
- mRemoteEventTriggerTimestampMillis = opts.getLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
@@ -807,60 +789,6 @@
}
/**
- * Set the timestamp for the event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * <p> For instance, if this broadcast is for a push message, then this timestamp
- * could correspond to when the device received the message.
- *
- * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that
- * correspond to the event that triggered this broadcast.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public void setEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) {
- mEventTriggerTimestampMillis = timestampMillis;
- }
-
- /**
- * Return the timestamp for the event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously
- * set using {@link #setEventTriggerTimestampMillis(long)}.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public @CurrentTimeMillisLong long getEventTriggerTimestampMillis() {
- return mEventTriggerTimestampMillis;
- }
-
- /**
- * Set the timestamp for the remote event, if any, that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * <p> For instance, if this broadcast is for a push message, then this timestamp
- * could correspond to when the message originated remotely.
- *
- * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that
- * correspond to the remote event that triggered this broadcast.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public void setRemoteEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) {
- mRemoteEventTriggerTimestampMillis = timestampMillis;
- }
-
- /**
- * Return the timestamp for the remote event that triggered this broadcast, in
- * {@link System#currentTimeMillis()} timebase.
- *
- * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously
- * set using {@link #setRemoteEventTriggerTimestampMillis(long)}}.
- */
- @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS)
- public @CurrentTimeMillisLong long getRemoteEventTriggerTimestampMillis() {
- return mRemoteEventTriggerTimestampMillis;
- }
-
- /**
* Sets deferral policy for this broadcast that specifies how this broadcast
* can be deferred for delivery at some future point.
*/
@@ -1195,12 +1123,6 @@
if (mIdForResponseEvent != 0) {
b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
}
- if (mEventTriggerTimestampMillis > 0) {
- b.putLong(KEY_EVENT_TRIGGER_TIMESTAMP, mEventTriggerTimestampMillis);
- }
- if (mRemoteEventTriggerTimestampMillis > 0) {
- b.putLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP, mRemoteEventTriggerTimestampMillis);
- }
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 5e6b54b..84bc6ce 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -962,4 +962,12 @@
*/
oneway void frozenBinderTransactionDetected(int debugPid, int code, int flags, int err);
int getBindingUidProcessState(int uid, in String callingPackage);
+
+ /**
+ * Return the timestampe (in the elapsed timebase) when the UID became idle from active
+ * last time (regardless of if the UID is still idle, or became active again).
+ * This is useful when trying to detect whether an UID has ever became idle since a certain
+ * time in the past.
+ */
+ long getUidLastIdleElapsedTime(int uid, in String callingPackage);
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 08636ae..55ce90d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -157,7 +157,6 @@
ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
int userId);
boolean isTopActivityImmersive();
- ActivityManager.TaskDescription getTaskDescription(int taskId);
void reportAssistContextExtras(in IBinder assistToken, in Bundle extras,
in AssistStructure structure, in AssistContent content, in Uri referrer);
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 41b97d0..97c2e43 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -42,6 +42,7 @@
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
per-file ActivityThread.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ActivityThread.java = file:RESOURCES_OWNERS
# Alarm
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
diff --git a/core/java/android/app/RESOURCES_OWNERS b/core/java/android/app/RESOURCES_OWNERS
index 5582803..fe37c0c 100644
--- a/core/java/android/app/RESOURCES_OWNERS
+++ b/core/java/android/app/RESOURCES_OWNERS
@@ -1,3 +1,5 @@
-rtmitchell@google.com
-toddke@google.com
+zyy@google.com
+jakmcbane@google.com
+branliu@google.com
+markpun@google.com
patb@google.com
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index b1e7b62..370aff8 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,6 +120,17 @@
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
/**
+ * A list of Resources references for all Resources instances created through Resources public
+ * constructor, only system Resources created by the private constructor are excluded.
+ * This addition is necessary due to certain Application Resources created by constructor
+ * directly which are not managed by ResourcesManager, hence we require a comprehensive
+ * collection of all Resources references to help with asset paths appending tasks when shared
+ * libraries are registered.
+ */
+ private final ArrayList<WeakReference<Resources>> mAllResourceReferences = new ArrayList<>();
+ private final ReferenceQueue<Resources> mAllResourceReferencesQueue = new ReferenceQueue<>();
+
+ /**
* The localeConfig of the app.
*/
private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
@@ -1568,7 +1579,7 @@
}
}
- redirectResourcesToNewImplLocked(updatedResourceKeys);
+ redirectAllResourcesToNewImplLocked(updatedResourceKeys);
}
}
@@ -1707,6 +1718,43 @@
}
}
+ // Another redirect function which will loop through all Resources and reload ResourcesImpl
+ // if it needs a shared library asset paths update.
+ private void redirectAllResourcesToNewImplLocked(
+ @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
+ cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue);
+
+ // Update any references to ResourcesImpl that require reloading.
+ final int resourcesCount = mAllResourceReferences.size();
+ for (int i = 0; i < resourcesCount; i++) {
+ final WeakReference<Resources> ref = mAllResourceReferences.get(i);
+ final Resources r = ref != null ? ref.get() : null;
+ if (r != null) {
+ final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+ if (key != null) {
+ final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+ if (impl == null) {
+ throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+ }
+ r.setImpl(impl);
+ } else {
+ // ResourcesKey is null which means the ResourcesImpl could belong to a
+ // Resources created by application through Resources constructor and was not
+ // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
+ // have shared library asset paths appended if there are any.
+ if (r.getImpl() != null) {
+ final ResourcesImpl oldImpl = r.getImpl();
+ // ResourcesImpl constructor will help to append shared library asset paths.
+ final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
+ oldImpl.getMetrics(), oldImpl.getConfiguration(),
+ oldImpl.getDisplayAdjustments());
+ r.setImpl(newImpl);
+ }
+ }
+ }
+ }
+ }
+
/**
* Returns the LocaleConfig current set
*/
@@ -1827,4 +1875,17 @@
public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
return new ArrayMap<>(mSharedLibAssetsMap);
}
+
+ /**
+ * Add all resources references to the list which is designed to help to append shared library
+ * asset paths. This is invoked in Resources constructor to include all Resources instances.
+ */
+ public void registerAllResourcesReference(@NonNull Resources resources) {
+ if (android.content.res.Flags.registerResourcePaths()) {
+ synchronized (mLock) {
+ mAllResourceReferences.add(
+ new WeakReference<>(resources, mAllResourceReferencesQueue));
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 4fa45be..25697c5 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -174,6 +174,17 @@
}
flag {
+ name: "copy_account_with_retry_enabled"
+ namespace: "enterprise"
+ description: "Retry copy and remove account from personal to work profile in case of failure"
+ bug: "329424312"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
name: "esim_management_ux_enabled"
namespace: "enterprise"
description: "Enable UX changes for esim management"
@@ -186,8 +197,8 @@
description: "Fix provisioning for single-user headless DO"
bug: "289515470"
metadata {
- purpose: PURPOSE_BUGFIX
- }
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -203,3 +214,13 @@
description: "Guards a new flow for recursive required enterprise app list merging"
bug: "319084618"
}
+
+flag {
+ name: "headless_device_owner_delegate_security_logging_bug_fix"
+ namespace: "enterprise"
+ description: "Fix delegate security logging for single user headless DO."
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java
new file mode 100644
index 0000000..378193f
--- /dev/null
+++ b/core/java/android/app/contextualsearch/CallbackToken.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.contextualsearch;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.contextualsearch.flags.Flags;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Used to share a single use token with the contextual search handling activity via the launch
+ * extras bundle.
+ * The caller can then use this token to get {@link ContextualSearchState} by calling
+ * {@link #getContextualSearchState}.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
+@SystemApi
+public final class CallbackToken implements Parcelable {
+ private static final boolean DEBUG = true;
+ private static final String TAG = CallbackToken.class.getSimpleName();
+ private final IBinder mToken;
+
+ private final Object mLock = new Object();
+ private boolean mTokenUsed = false;
+
+ public CallbackToken() {
+ mToken = new Binder();
+ }
+
+ private CallbackToken(Parcel in) {
+ mToken = in.readStrongBinder();
+ }
+
+ /**
+ * Returns the {@link ContextualSearchState} to the handler via the provided callback. The
+ * method can only be invoked to provide the {@link OutcomeReceiver} once and all subsequent
+ * invocations of this method will result in {@link OutcomeReceiver#onError} being called with
+ * an {@link IllegalAccessException}.
+ *
+ * @param executor The executor which will be used to invoke the callback.
+ * @param callback The callback which will be used to return {@link ContextualSearchState}
+ * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
+ * used to return errors via {@link OutcomeReceiver#onError}.
+ */
+ public void getContextualSearchState(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken);
+ boolean tokenUsed;
+ synchronized (mLock) {
+ tokenUsed = markUsedLocked();
+ }
+ if (tokenUsed) {
+ callback.onError(new IllegalAccessException("Token already used."));
+ return;
+ }
+ try {
+ // Get the service from the system server.
+ IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE);
+ IContextualSearchManager service = IContextualSearchManager.Stub.asInterface(b);
+ final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
+ // If the service is not null, hand over the call to the service.
+ if (service != null) {
+ service.getContextualSearchState(mToken, wrapper);
+ } else {
+ Log.w(TAG, "Failed to getContextualSearchState. Service null.");
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) Log.d(TAG, "Failed to call getContextualSearchState", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private boolean markUsedLocked() {
+ boolean oldValue = mTokenUsed;
+ mTokenUsed = true;
+ return oldValue;
+ }
+
+ /**
+ * Return the token necessary for validating the caller of {@link #getContextualSearchState}.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mToken);
+ }
+
+ @NonNull
+ public static final Creator<CallbackToken> CREATOR = new Creator<>() {
+ @Override
+ public CallbackToken createFromParcel(Parcel in) {
+ return new CallbackToken(in);
+ }
+
+ @Override
+ public CallbackToken[] newArray(int size) {
+ return new CallbackToken[size];
+ }
+ };
+
+ private static class CallbackWrapper extends IContextualSearchCallback.Stub {
+ private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onResult(ContextualSearchState state) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.d(TAG, "onResult state:" + state);
+ mExecutor.execute(() -> mCallback.onResult(state));
+ });
+ }
+
+ @Override
+ public void onError(ParcelableException error) {
+ Binder.withCleanCallingIdentity(() -> {
+ if (DEBUG) Log.w(TAG, "onError", error);
+ mExecutor.execute(() -> mCallback.onError(error));
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 693de21..c080a6b 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -18,43 +18,32 @@
import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH;
-import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.contextualsearch.flags.Flags;
import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
-import android.os.OutcomeReceiver;
-import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.Executor;
/**
* {@link ContextualSearchManager} is a system service to facilitate contextual search experience on
* configured Android devices.
* <p>
- * This class lets
- * <ul>
- * <li> a caller start contextual search by calling {@link #startContextualSearch} method.
- * <li> a handler request {@link ContextualSearchState} by calling the
- * {@link #getContextualSearchState} method.
- * </ul>
+ * This class lets a caller start contextual search by calling {@link #startContextualSearch}
+ * method.
*
* @hide
*/
@SystemApi
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
-public class ContextualSearchManager {
+public final class ContextualSearchManager {
/**
* Key to get the entrypoint from the extras of the activity launched by contextual search.
@@ -92,7 +81,7 @@
/**
* Key to get the binder token from the extras of the activity launched by contextual search.
- * This token is needed to invoke {@link #getContextualSearchState} method.
+ * This token is needed to invoke {@link CallbackToken#getContextualSearchState} method.
* Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
*/
public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
@@ -174,57 +163,4 @@
e.rethrowFromSystemServer();
}
}
-
- /**
- * Returns the {@link ContextualSearchState} to the handler via the provided callback.
- *
- * @param token The caller is expected to get the token from the launch extras of the handling
- * activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key.
- * <br>
- * <b>Note</b> This token is for one time use only. Subsequent uses will invoke
- * callback's {@link OutcomeReceiver#onError}.
- * @param executor The executor which will be used to invoke the callback.
- * @param callback The callback which will be used to return {@link ContextualSearchState}
- * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be
- * used to return errors via {@link OutcomeReceiver#onError}.
- */
- public void getContextualSearchState(@NonNull IBinder token,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
- if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token);
- try {
- final CallbackWrapper wrapper = new CallbackWrapper(executor, callback);
- mService.getContextualSearchState(token, wrapper);
- } catch (RemoteException e) {
- if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e);
- e.rethrowFromSystemServer();
- }
- }
-
- private static class CallbackWrapper extends IContextualSearchCallback.Stub {
- private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback;
- private final Executor mExecutor;
-
- CallbackWrapper(@NonNull Executor callbackExecutor,
- @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) {
- mCallback = callback;
- mExecutor = callbackExecutor;
- }
-
- @Override
- public void onResult(ContextualSearchState state) {
- Binder.withCleanCallingIdentity(() -> {
- if (DEBUG) Log.d(TAG, "onResult state:" + state);
- mExecutor.execute(() -> mCallback.onResult(state));
- });
- }
-
- @Override
- public void onError(ParcelableException error) {
- Binder.withCleanCallingIdentity(() -> {
- if (DEBUG) Log.w(TAG, "onError", error);
- mExecutor.execute(() -> mCallback.onError(error));
- });
- }
- }
}
diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java
index 5c04bc89..f01ae55 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchState.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchState.java
@@ -32,6 +32,10 @@
* {@link ContextualSearchState} contains additional data a contextual search handler can request
* via {@link ContextualSearchManager#getContextualSearchState} method.
*
+ * It provides the caller of {@link ContextualSearchManager#getContextualSearchState} with an
+ * {@link AssistStructure}, {@link AssistContent} and a {@link Bundle} extras containing any
+ * relevant data added by th system server. When invoked via the Launcher, this bundle is empty.
+ *
* @hide
*/
@FlaggedApi(Flags.FLAG_ENABLE_SERVICE)
@@ -41,6 +45,12 @@
private final @Nullable AssistStructure mStructure;
private final @Nullable AssistContent mContent;
+ /**
+ * {@link ContextualSearchState} contains non-essential data which can be requested by the
+ * Contextual Search activity.
+ * The activity can request an instance of {@link ContextualSearchState} by calling
+ * {@link CallbackToken#getContextualSearchState} and passing a valid token as an argument.
+ */
public ContextualSearchState(@Nullable AssistStructure structure,
@Nullable AssistContent content, @NonNull Bundle extras) {
mStructure = structure;
diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
index 1735a71..9b0b8b7 100644
--- a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
+++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl
@@ -1,6 +1,5 @@
package android.app.contextualsearch;
-
import android.app.contextualsearch.IContextualSearchCallback;
/**
* @hide
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 475ee7a..5514868 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -41,7 +41,12 @@
*
* <p>They are constructed using {@link Builder} in a tree structure
* that provides the OS some information about how the content should be displayed.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class Slice implements Parcelable {
/**
@@ -338,7 +343,12 @@
/**
* A Builder used to construct {@link Slice}s
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class Builder {
private final Uri mUri;
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 2d6f4a6..27c726d 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -53,7 +53,12 @@
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
* are defined on {@link Slice}.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class SliceItem implements Parcelable {
private static final String TAG = "SliceItem";
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 2e179d0..4fc2a0c 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -59,7 +59,12 @@
* Class to handle interactions with {@link Slice}s.
* <p>
* The SliceManager manages permissions and pinned state for slices.
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
@SystemService(Context.SLICE_SERVICE)
public class SliceManager {
diff --git a/core/java/android/app/slice/SliceMetrics.java b/core/java/android/app/slice/SliceMetrics.java
index 746beaf..abfe3a2f 100644
--- a/core/java/android/app/slice/SliceMetrics.java
+++ b/core/java/android/app/slice/SliceMetrics.java
@@ -31,7 +31,12 @@
* not need to reference this class.
*
* @see androidx.slice.widget.SliceView
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public class SliceMetrics {
private static final String TAG = "SliceMetrics";
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 42c3aa6..4374550 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -91,7 +91,12 @@
* </pre>
*
* @see Slice
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public abstract class SliceProvider extends ContentProvider {
/**
* This is the Android platform's MIME type for a URI
diff --git a/core/java/android/app/slice/SliceSpec.java b/core/java/android/app/slice/SliceSpec.java
index a332349..078f552 100644
--- a/core/java/android/app/slice/SliceSpec.java
+++ b/core/java/android/app/slice/SliceSpec.java
@@ -38,7 +38,12 @@
*
* @see Slice
* @see SliceProvider#onBindSlice(Uri, Set)
+ * @deprecated Slice framework has been deprecated, it will not receive any updates from
+ * {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
+ * framework that sends displayable data from one app to another, consider using
+ * {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public final class SliceSpec implements Parcelable {
private final String mType;
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2c26389..a08d659 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -26,6 +26,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -80,25 +81,27 @@
import java.util.function.Consumer;
/**
- * System level service for managing companion devices
+ * Public interfaces for managing companion devices.
*
- * See <a href="{@docRoot}guide/topics/connectivity/companion-device-pairing">this guide</a>
- * for a usage example.
+ * <p>The interfaces in this class allow companion apps to
+ * {@link #associate(AssociationRequest, Executor, Callback)} discover and request device profiles}
+ * for companion devices, {@link #startObservingDevicePresence(String) listen to device presence
+ * events}, {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver) transfer system level
+ * data} via {@link #attachSystemDataTransport(int, InputStream, OutputStream) the reported
+ * channel} and more.</p>
*
- * <p>To obtain an instance call {@link Context#getSystemService}({@link
- * Context#COMPANION_DEVICE_SERVICE}) Then, call {@link #associate(AssociationRequest,
- * Callback, Handler)} to initiate the flow of associating current package with a
- * device selected by user.</p>
- *
- * @see CompanionDeviceManager#associate
- * @see AssociationRequest
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about managing companion devices, read the <a href=
+ * "{@docRoot}guide/topics/connectivity/companion-device-pairing">Companion Device Pairing</a>
+ * developer guide.
+ * </div>
*/
@SuppressLint("LongLogTag")
@SystemService(Context.COMPANION_DEVICE_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
public final class CompanionDeviceManager {
-
- private static final boolean DEBUG = false;
- private static final String LOG_TAG = "CDM_CompanionDeviceManager";
+ private static final String TAG = "CDM_CompanionDeviceManager";
/** @hide */
@IntDef(prefix = {"RESULT_"}, value = {
@@ -374,7 +377,7 @@
}
private final ICompanionDeviceManager mService;
- private Context mContext;
+ private final Context mContext;
@GuardedBy("mListeners")
private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>();
@@ -432,7 +435,11 @@
@NonNull AssociationRequest request,
@NonNull Callback callback,
@Nullable Handler handler) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
Objects.requireNonNull(request, "Request cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
handler = Handler.mainIfNull(handler);
@@ -492,7 +499,11 @@
@NonNull AssociationRequest request,
@NonNull Executor executor,
@NonNull Callback callback) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
Objects.requireNonNull(request, "Request cannot be null");
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
@@ -521,7 +532,10 @@
@UserHandleAware
@Nullable
public IntentSender buildAssociationCancellationIntent() {
- if (!checkFeaturePresent()) return null;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return null;
+ }
try {
PendingIntent pendingIntent = mService.buildAssociationCancellationIntent(
@@ -543,7 +557,8 @@
* @param flags system data types to be enabled.
*/
public void enableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
@@ -565,7 +580,8 @@
* @param flags system data types to be disabled.
*/
public void disableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
@@ -580,6 +596,11 @@
* @hide
*/
public void enablePermissionsSync(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.enablePermissionsSync(associationId);
} catch (RemoteException e) {
@@ -591,6 +612,11 @@
* @hide
*/
public void disablePermissionsSync(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.disablePermissionsSync(associationId);
} catch (RemoteException e) {
@@ -602,6 +628,11 @@
* @hide
*/
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return null;
+ }
+
try {
return mService.getPermissionSyncRequest(associationId);
} catch (RemoteException e) {
@@ -636,7 +667,10 @@
@UserHandleAware
@NonNull
public List<AssociationInfo> getMyAssociations() {
- if (!checkFeaturePresent()) return Collections.emptyList();
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return Collections.emptyList();
+ }
try {
return mService.getAssociations(mContext.getOpPackageName(), mContext.getUserId());
@@ -665,7 +699,10 @@
@UserHandleAware
@Deprecated
public void disassociate(@NonNull String deviceMacAddress) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
try {
mService.legacyDisassociate(deviceMacAddress, mContext.getOpPackageName(),
@@ -690,7 +727,10 @@
*/
@UserHandleAware
public void disassociate(int associationId) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
try {
mService.disassociate(associationId);
@@ -716,9 +756,11 @@
*/
@UserHandleAware
public void requestNotificationAccess(ComponentName component) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
+
try {
PendingIntent pendingIntent = mService.requestNotificationAccess(
component, mContext.getUserId());
@@ -755,9 +797,11 @@
*/
@Deprecated
public boolean hasNotificationAccess(ComponentName component) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return false;
}
+
try {
return mService.hasNotificationAccess(component);
} catch (RemoteException e) {
@@ -793,7 +837,11 @@
@NonNull String packageName,
@NonNull MacAddress macAddress,
@NonNull UserHandle user) {
- if (!checkFeaturePresent()) return false;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return false;
+ }
+
Objects.requireNonNull(packageName, "package name cannot be null");
Objects.requireNonNull(macAddress, "mac address cannot be null");
Objects.requireNonNull(user, "user cannot be null");
@@ -816,7 +864,8 @@
@SystemApi
@UserHandleAware
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
- public @NonNull List<AssociationInfo> getAllAssociations() {
+ @NonNull
+ public List<AssociationInfo> getAllAssociations() {
return getAllAssociations(mContext.getUserId());
}
@@ -826,8 +875,13 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
- public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) {
- if (!checkFeaturePresent()) return Collections.emptyList();
+ @NonNull
+ public List<AssociationInfo> getAllAssociations(@UserIdInt int userId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return Collections.emptyList();
+ }
+
try {
return mService.getAllAssociationsForUser(userId);
} catch (RemoteException e) {
@@ -875,7 +929,11 @@
public void addOnAssociationsChangedListener(
@NonNull Executor executor, @NonNull OnAssociationsChangedListener listener,
@UserIdInt int userId) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
synchronized (mListeners) {
final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy(
executor, listener);
@@ -899,7 +957,11 @@
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void removeOnAssociationsChangedListener(
@NonNull OnAssociationsChangedListener listener) {
- if (!checkFeaturePresent()) return;
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
synchronized (mListeners) {
final Iterator<OnAssociationsChangedListenerProxy> iterator = mListeners.iterator();
while (iterator.hasNext()) {
@@ -931,6 +993,11 @@
public void addOnTransportsChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<List<AssociationInfo>> listener) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
executor, listener);
try {
@@ -950,6 +1017,11 @@
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
@NonNull Consumer<List<AssociationInfo>> listener) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
null, listener);
try {
@@ -969,6 +1041,11 @@
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.sendMessage(messageType, data, associationIds);
} catch (RemoteException e) {
@@ -989,6 +1066,11 @@
public void addOnMessageReceivedListener(
@NonNull @CallbackExecutor Executor executor, int messageType,
@NonNull BiConsumer<Integer, byte[]> listener) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
executor, listener);
try {
@@ -1006,6 +1088,11 @@
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
@NonNull BiConsumer<Integer, byte[]> listener) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
null, listener);
try {
@@ -1031,9 +1118,11 @@
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public boolean canPairWithoutPrompt(@NonNull String packageName,
@NonNull String deviceMacAddress, @NonNull UserHandle user) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return false;
}
+
Objects.requireNonNull(packageName, "package name cannot be null");
Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null");
Objects.requireNonNull(user, "user handle cannot be null");
@@ -1081,9 +1170,11 @@
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void startObservingDevicePresence(@NonNull String deviceAddress)
throws DeviceNotAssociatedException {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
+
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
mService.legacyStartObservingDevicePresence(deviceAddress,
@@ -1123,9 +1214,11 @@
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void stopObservingDevicePresence(@NonNull String deviceAddress)
throws DeviceNotAssociatedException {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
+
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
mService.legacyStopObservingDevicePresence(deviceAddress,
@@ -1171,6 +1264,11 @@
@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void startObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
Objects.requireNonNull(request, "request cannot be null");
try {
@@ -1192,6 +1290,11 @@
@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
public void stopObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
Objects.requireNonNull(request, "request cannot be null");
try {
@@ -1222,7 +1325,7 @@
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message)
throws DeviceNotAssociatedException {
- Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport");
+ Log.w(TAG, "dispatchMessage replaced by attachSystemDataTransport");
}
/**
@@ -1244,6 +1347,11 @@
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public void attachSystemDataTransport(int associationId, @NonNull InputStream in,
@NonNull OutputStream out) throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
detachSystemDataTransport(associationId);
@@ -1272,6 +1380,11 @@
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public void detachSystemDataTransport(int associationId)
throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
synchronized (mTransports) {
final Transport transport = mTransports.get(associationId);
if (transport != null) {
@@ -1296,9 +1409,11 @@
@NonNull String packageName,
@NonNull MacAddress macAddress,
@NonNull byte[] certificate) {
- if (!checkFeaturePresent()) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
return;
}
+
Objects.requireNonNull(packageName, "package name cannot be null");
Objects.requireNonNull(macAddress, "mac address cannot be null");
@@ -1327,6 +1442,11 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceAppeared(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.notifySelfManagedDeviceAppeared(associationId);
} catch (RemoteException e) {
@@ -1349,6 +1469,11 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceDisappeared(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.notifySelfManagedDeviceDisappeared(associationId);
} catch (RemoteException e) {
@@ -1384,6 +1509,11 @@
@Nullable
public IntentSender buildPermissionTransferUserConsentIntent(int associationId)
throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return null;
+ }
+
try {
PendingIntent pendingIntent = mService.buildPermissionTransferUserConsentIntent(
mContext.getOpPackageName(),
@@ -1420,6 +1550,11 @@
@UserHandleAware
@FlaggedApi(Flags.FLAG_PERM_SYNC_USER_CONSENT)
public boolean isPermissionTransferUserConsented(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return false;
+ }
+
try {
return mService.isPermissionTransferUserConsented(mContext.getOpPackageName(),
mContext.getUserId(), associationId);
@@ -1446,6 +1581,11 @@
@Deprecated
@UserHandleAware
public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
associationId, null);
@@ -1478,6 +1618,11 @@
@NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CompanionException> result)
throws DeviceNotAssociatedException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
associationId, new SystemDataTransferCallbackProxy(executor, result));
@@ -1495,6 +1640,11 @@
*/
@UserHandleAware
public boolean isCompanionApplicationBound() {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return false;
+ }
+
try {
return mService.isCompanionApplicationBound(
mContext.getOpPackageName(), mContext.getUserId());
@@ -1513,6 +1663,11 @@
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void enableSecureTransport(boolean enabled) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.enableSecureTransport(enabled);
} catch (RemoteException e) {
@@ -1534,6 +1689,11 @@
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void setAssociationTag(int associationId, @NonNull String tag) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
Objects.requireNonNull(tag, "tag cannot be null");
if (tag.length() > ASSOCIATION_TAG_LENGTH_LIMIT) {
@@ -1560,6 +1720,11 @@
@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void clearAssociationTag(int associationId) {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
try {
mService.clearAssociationTag(associationId);
} catch (RemoteException e) {
@@ -1567,15 +1732,6 @@
}
}
- private boolean checkFeaturePresent() {
- boolean featurePresent = mService != null;
- if (!featurePresent && DEBUG) {
- Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP
- + " not available");
- }
- return featurePresent;
- }
-
private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub {
private final Handler mHandler;
private final Callback mCallback;
@@ -1613,7 +1769,7 @@
private <T> void execute(Consumer<T> callback, T arg) {
if (mExecutor != null) {
mExecutor.execute(() -> callback.accept(arg));
- } else {
+ } else if (mHandler != null) {
mHandler.post(() -> callback.accept(arg));
}
}
@@ -1716,6 +1872,11 @@
}
public void start() throws IOException {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
final ParcelFileDescriptor localFd = pair[0];
final ParcelFileDescriptor remoteFd = pair[1];
@@ -1734,7 +1895,7 @@
copyWithFlushing(mLocalIn, mRemoteOut);
} catch (IOException e) {
if (!mStopped) {
- Log.w(LOG_TAG, "Trouble during outgoing transport", e);
+ Log.w(TAG, "Trouble during outgoing transport", e);
stop();
}
}
@@ -1744,7 +1905,7 @@
copyWithFlushing(mRemoteIn, mLocalOut);
} catch (IOException e) {
if (!mStopped) {
- Log.w(LOG_TAG, "Trouble during incoming transport", e);
+ Log.w(TAG, "Trouble during incoming transport", e);
stop();
}
}
@@ -1752,13 +1913,18 @@
}
public void stop() {
+ if (mService == null) {
+ Log.w(TAG, "CompanionDeviceManager service is not available.");
+ return;
+ }
+
mStopped = true;
try {
mService.detachSystemDataTransport(mContext.getPackageName(),
mContext.getUserId(), mAssociationId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Failed to detach transport", e);
+ Log.w(TAG, "Failed to detach transport", e);
}
IoUtils.closeQuietly(mRemoteIn);
diff --git a/core/java/android/content/ComponentCallbacks.java b/core/java/android/content/ComponentCallbacks.java
index 428545d..fb9536f 100644
--- a/core/java/android/content/ComponentCallbacks.java
+++ b/core/java/android/content/ComponentCallbacks.java
@@ -55,15 +55,11 @@
* That is, before reaching the point of killing processes hosting
* service and foreground UI that we would like to avoid killing.
*
- * <p>You should implement this method to release
- * any caches or other unnecessary resources you may be holding on to.
- * The system will perform a garbage collection for you after returning from this method.
- * <p>Preferably, you should implement {@link ComponentCallbacks2#onTrimMemory} from
- * {@link ComponentCallbacks2} to incrementally unload your resources based on various
- * levels of memory demands. That API is available for API level 14 and higher, so you should
- * only use this {@link #onLowMemory} method as a fallback for older versions, which can be
- * treated the same as {@link ComponentCallbacks2#onTrimMemory} with the {@link
- * ComponentCallbacks2#TRIM_MEMORY_COMPLETE} level.</p>
+ * @deprecated Since API level 14 this is superseded by
+ * {@link ComponentCallbacks2#onTrimMemory}.
+ * Since API level 34 this is never called.
+ * Apps targeting API level 34 and above may provide an empty implementation.
*/
+ @Deprecated
void onLowMemory();
}
diff --git a/core/java/android/content/ComponentCallbacks2.java b/core/java/android/content/ComponentCallbacks2.java
index 6576c0f..a165b18 100644
--- a/core/java/android/content/ComponentCallbacks2.java
+++ b/core/java/android/content/ComponentCallbacks2.java
@@ -105,14 +105,20 @@
* Level for {@link #onTrimMemory(int)}: the process is nearing the end
* of the background LRU list, and if more memory isn't found soon it will
* be killed.
+ *
+ * @deprecated Apps are not notified of this level since API level 34
*/
+ @Deprecated
static final int TRIM_MEMORY_COMPLETE = 80;
/**
* Level for {@link #onTrimMemory(int)}: the process is around the middle
* of the background LRU list; freeing memory can help the system keep
* other processes running later in the list for better overall performance.
+ *
+ * @deprecated Apps are not notified of this level since API level 34
*/
+ @Deprecated
static final int TRIM_MEMORY_MODERATE = 60;
/**
@@ -139,7 +145,10 @@
* will happen after this is {@link #onLowMemory()} called to report that
* nothing at all can be kept in the background, a situation that can start
* to notably impact the user.
+ *
+ * @deprecated Apps are not notified of this level since API level 34
*/
+ @Deprecated
static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
/**
@@ -147,7 +156,10 @@
* background process, but the device is running low on memory.
* Your running process should free up unneeded resources to allow that
* memory to be used elsewhere.
+ *
+ * @deprecated Apps are not notified of this level since API level 34
*/
+ @Deprecated
static final int TRIM_MEMORY_RUNNING_LOW = 10;
/**
@@ -155,17 +167,19 @@
* background process, but the device is running moderately low on memory.
* Your running process may want to release some unneeded resources for
* use elsewhere.
+ *
+ * @deprecated Apps are not notified of this level since API level 34
*/
+ @Deprecated
static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
/**
* Called when the operating system has determined that it is a good
- * time for a process to trim unneeded memory from its process. This will
- * happen for example when it goes in the background and there is not enough
- * memory to keep as many background processes running as desired. You
- * should never compare to exact values of the level, since new intermediate
- * values may be added -- you will typically want to compare if the value
- * is greater or equal to a level you are interested in.
+ * time for a process to trim unneeded memory from its process.
+ *
+ * You should never compare to exact values of the level, since new
+ * intermediate values may be added -- you will typically want to compare if
+ * the value is greater or equal to a level you are interested in.
*
* <p>To retrieve the processes current trim level at any point, you can
* use {@link android.app.ActivityManager#getMyMemoryState
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index bff90f1..5d4babb 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -847,5 +847,5 @@
@EnforcePermission("GET_APP_METADATA")
int getAppMetadataSource(String packageName, int userId);
- ComponentName getDomainVerificationAgent();
+ ComponentName getDomainVerificationAgent(int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4c0da7c..f5bff9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,6 +4844,16 @@
public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
"android.hardware.rotaryencoder.lowres";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * support for contextual search helper.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
+ "android.software.contextualsearch";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1f5f88f..a720b64 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -339,14 +339,17 @@
public Resources(@Nullable ClassLoader classLoader) {
mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
sResourcesHistory.add(this);
+ ResourcesManager.getInstance().registerAllResourcesReference(this);
}
/**
- * Only for creating the System resources.
+ * Only for creating the System resources. This is the only constructor that doesn't add
+ * Resources itself to the ResourcesManager list of all Resources references.
*/
@UnsupportedAppUsage
private Resources() {
- this(null);
+ mClassLoader = ClassLoader.getSystemClassLoader();
+ sResourcesHistory.add(this);
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 4c22fee..31cacb7 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -228,6 +228,11 @@
return mAssets;
}
+ @UnsupportedAppUsage
+ public DisplayMetrics getMetrics() {
+ return mMetrics;
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
DisplayMetrics getDisplayMetrics() {
if (DEBUG_CONFIG) Slog.v(TAG, "Returning DisplayMetrics: " + mMetrics.widthPixels
@@ -235,7 +240,8 @@
return mMetrics;
}
- Configuration getConfiguration() {
+ @UnsupportedAppUsage
+ public Configuration getConfiguration() {
return mConfiguration;
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 1c36a88..a9f70c9 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -16,14 +16,21 @@
package android.hardware;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.ENODEV;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
@@ -56,6 +63,7 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Objects;
/**
* The Camera class is used to set image capture settings, start/stop preview,
@@ -271,7 +279,24 @@
* @return total number of accessible camera devices, or 0 if there are no
* cameras or an error was encountered enumerating them.
*/
- public native static int getNumberOfCameras();
+ public static int getNumberOfCameras() {
+ return getNumberOfCameras(ActivityThread.currentApplication().getApplicationContext());
+ }
+
+ /**
+ * Returns the number of physical cameras available on this device for the given context.
+ * The return value of this method might change dynamically if the device supports external
+ * cameras and an external camera is connected or disconnected.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public static int getNumberOfCameras(@NonNull Context context) {
+ return _getNumberOfCameras(context.getDeviceId(), getDevicePolicyFromContext(context));
+ }
+
+ private static native int _getNumberOfCameras(int deviceId, int devicePolicy);
/**
* Returns the information about a particular camera.
@@ -282,10 +307,22 @@
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
- boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
- ActivityThread.currentApplication().getApplicationContext());
+ Context context = ActivityThread.currentApplication().getApplicationContext();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
+ getCameraInfo(cameraId, context, overrideToPortrait, cameraInfo);
+ }
- _getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
+ /**
+ * Returns the information about a particular camera for the given context.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public static void getCameraInfo(int cameraId, @NonNull Context context,
+ boolean overrideToPortrait, CameraInfo cameraInfo) {
+ _getCameraInfo(cameraId, overrideToPortrait, context.getDeviceId(),
+ getDevicePolicyFromContext(context), cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
try {
@@ -298,8 +335,20 @@
Log.e(TAG, "Audio service is unavailable for queries");
}
}
+
private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
- CameraInfo cameraInfo);
+ int deviceId, int devicePolicy, CameraInfo cameraInfo);
+
+ private static int getDevicePolicyFromContext(Context context) {
+ if (context.getDeviceId() == DEVICE_ID_DEFAULT
+ || !android.companion.virtual.flags.Flags.virtualCamera()) {
+ return DEVICE_POLICY_DEFAULT;
+ }
+
+ VirtualDeviceManager virtualDeviceManager =
+ context.getSystemService(VirtualDeviceManager.class);
+ return virtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
+ }
/**
* Information about a camera
@@ -359,7 +408,7 @@
* when {@link Camera#takePicture takePicture} is called.</p>
*/
public boolean canDisableShutterSound;
- };
+ }
/**
* Creates a new Camera object to access a particular hardware camera. If
@@ -391,7 +440,20 @@
* @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
*/
public static Camera open(int cameraId) {
- return new Camera(cameraId);
+ Context context = ActivityThread.currentApplication().getApplicationContext();
+ boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(context);
+ return open(cameraId, context, overrideToPortrait);
+ }
+
+ /**
+ * Creates a new Camera object for a given camera id for the given context.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public static Camera open(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
+ return new Camera(cameraId, context, overrideToPortrait);
}
/**
@@ -409,7 +471,7 @@
for (int i = 0; i < numberOfCameras; i++) {
getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- return new Camera(i);
+ return open(i);
}
}
return null;
@@ -459,10 +521,10 @@
throw new IllegalArgumentException("Unsupported HAL version " + halVersion);
}
- return new Camera(cameraId);
+ return open(cameraId);
}
- private int cameraInit(int cameraId) {
+ private int cameraInit(int cameraId, Context context, boolean overrideToPortrait) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -480,11 +542,10 @@
mEventHandler = null;
}
- boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait(
- ActivityThread.currentApplication().getApplicationContext());
boolean forceSlowJpegMode = shouldForceSlowJpegMode();
- return native_setup(new WeakReference<Camera>(this), cameraId,
- ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode);
+ return native_setup(new WeakReference<>(this), cameraId,
+ ActivityThread.currentOpPackageName(), overrideToPortrait, forceSlowJpegMode,
+ context.getDeviceId(), getDevicePolicyFromContext(context));
}
private boolean shouldForceSlowJpegMode() {
@@ -501,8 +562,9 @@
}
/** used by Camera#open, Camera#open(int) */
- Camera(int cameraId) {
- int err = cameraInit(cameraId);
+ Camera(int cameraId, @NonNull Context context, boolean overrideToPortrait) {
+ Objects.requireNonNull(context);
+ int err = cameraInit(cameraId, context, overrideToPortrait);
if (checkInitErrors(err)) {
if (err == -EACCES) {
throw new RuntimeException("Fail to connect to camera service");
@@ -515,7 +577,6 @@
initAppOps();
}
-
/**
* @hide
*/
@@ -568,11 +629,10 @@
@UnsupportedAppUsage
private native int native_setup(Object cameraThis, int cameraId, String packageName,
- boolean overrideToPortrait, boolean forceSlowJpegMode);
+ boolean overrideToPortrait, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
private native final void native_release();
-
/**
* Disconnects and releases the Camera object resources.
*
@@ -672,13 +732,15 @@
if (holder != null) {
setPreviewSurface(holder.getSurface());
} else {
- setPreviewSurface((Surface)null);
+ setPreviewSurface(null);
}
}
/**
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public native final void setPreviewSurface(Surface surface) throws IOException;
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index fa35efb..0198421 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -16,6 +16,7 @@
package android.hardware;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,7 +24,7 @@
* Status information about a camera.
*
* Contains the name of the camera device, and its current status, one of the
- * ICameraServiceListener.STATUS_ values.
+ * ICameraServiceListener.STATUS_* values.
*
* @hide
*/
@@ -32,6 +33,7 @@
public int status;
public String[] unavailablePhysicalCameras;
public String clientPackage;
+ public int deviceId;
@Override
public int describeContents() {
@@ -44,6 +46,7 @@
out.writeInt(status);
out.writeStringArray(unavailablePhysicalCameras);
out.writeString(clientPackage);
+ out.writeInt(deviceId);
}
public void readFromParcel(Parcel in) {
@@ -51,21 +54,22 @@
status = in.readInt();
unavailablePhysicalCameras = in.readStringArray();
clientPackage = in.readString();
+ deviceId = in.readInt();
}
- public static final @android.annotation.NonNull Parcelable.Creator<CameraStatus> CREATOR =
- new Parcelable.Creator<CameraStatus>() {
- @Override
- public CameraStatus createFromParcel(Parcel in) {
- CameraStatus status = new CameraStatus();
- status.readFromParcel(in);
+ @NonNull
+ public static final Parcelable.Creator<CameraStatus> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public CameraStatus createFromParcel(Parcel in) {
+ CameraStatus status = new CameraStatus();
+ status.readFromParcel(in);
+ return status;
+ }
- return status;
- }
-
- @Override
- public CameraStatus[] newArray(int size) {
- return new CameraStatus[size];
- }
- };
+ @Override
+ public CameraStatus[] newArray(int size) {
+ return new CameraStatus[size];
+ }
+ };
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d9614d1..90b7869 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -24,7 +24,7 @@
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
-import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import android.annotation.CallbackExecutor;
import android.annotation.DrawableRes;
@@ -529,7 +529,7 @@
/**
* Remove {@link Builder#setAllowBackgroundAuthentication(boolean)} once
- * FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE is enabled.
+ * FLAG_ALLOW_PRIVATE_PROFILE is enabled.
*
* @param allow If true, allows authentication when the calling package is not in the
* foreground. This is set to false by default.
@@ -538,7 +538,7 @@
* @return This builder
* @hide
*/
- @FlaggedApi(FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE)
+ @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
@TestApi
@NonNull
@RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 238c381..21c7004 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -19,6 +19,8 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.camera.VirtualCameraConfig;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.ExtensionKey;
@@ -5329,6 +5331,19 @@
new Key<Integer>("android.info.sessionConfigurationQueryVersion", int.class);
/**
+ * <p>Id of the device that owns this camera.</p>
+ * <p>In case of a virtual camera, this would be the id of the virtual device
+ * owning the camera. For any other camera, this key would not be present.
+ * Callers should assume {@link android.content.Context#DEVICE_ID_DEFAULT}
+ * in case this key is not present.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @see VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)
+ * @hide
+ */
+ public static final Key<Integer> INFO_DEVICE_ID =
+ new Key<Integer>("android.info.deviceId", int.class);
+
+ /**
* <p>The maximum number of frames that can occur after a request
* (different than the previous) has been submitted, and before the
* result's state becomes synchronized.</p>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b43a900..ea7db4c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -16,6 +16,10 @@
package android.hardware.camera2;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -25,6 +29,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
@@ -74,7 +79,9 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -105,8 +112,6 @@
private static final int CAMERA_TYPE_BACKWARD_COMPATIBLE = 0;
private static final int CAMERA_TYPE_ALL = 1;
- private ArrayList<String> mDeviceIdList;
-
private final Context mContext;
private final Object mLock = new Object();
@@ -114,6 +119,8 @@
"android.permission.CAMERA_OPEN_CLOSE_LISTENER";
private final boolean mHasOpenCloseListenerPermission;
+ private VirtualDeviceManager mVirtualDeviceManager;
+
/**
* Force camera output to be rotated to portrait orientation on landscape cameras.
* Many apps do not handle this situation and display stretched images otherwise.
@@ -240,7 +247,8 @@
*/
@NonNull
public String[] getCameraIdList() throws CameraAccessException {
- return CameraManagerGlobal.get().getCameraIdList();
+ return CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -251,11 +259,13 @@
* adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call
* affects the camera ids returned by getCameraIdList() as well. Tests which do adopt shell
* permission identity should not mix getCameraIdList() and getCameraListNoLazyCalls().
+ *
+ * @hide
*/
- /** @hide */
@TestApi
public String[] getCameraIdListNoLazy() throws CameraAccessException {
- return CameraManagerGlobal.get().getCameraIdListNoLazy();
+ return CameraManagerGlobal.get().getCameraIdListNoLazy(mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -381,7 +391,8 @@
public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
@Nullable Handler handler) {
CameraManagerGlobal.get().registerAvailabilityCallback(callback,
- CameraDeviceImpl.checkAndWrapHandler(handler), mHasOpenCloseListenerPermission);
+ CameraDeviceImpl.checkAndWrapHandler(handler), mHasOpenCloseListenerPermission,
+ mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
}
/**
@@ -420,7 +431,8 @@
throw new IllegalArgumentException("executor was null");
}
CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor,
- mHasOpenCloseListenerPermission);
+ mHasOpenCloseListenerPermission, mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -459,7 +471,8 @@
*/
public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) {
CameraManagerGlobal.get().registerTorchCallback(callback,
- CameraDeviceImpl.checkAndWrapHandler(handler));
+ CameraDeviceImpl.checkAndWrapHandler(handler), mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -480,7 +493,8 @@
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
- CameraManagerGlobal.get().registerTorchCallback(callback, executor);
+ CameraManagerGlobal.get().registerTorchCallback(callback, executor, mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -495,6 +509,19 @@
CameraManagerGlobal.get().unregisterTorchCallback(callback);
}
+ /** @hide */
+ public int getDevicePolicyFromContext(@NonNull Context context) {
+ if (context.getDeviceId() == DEVICE_ID_DEFAULT
+ || !android.companion.virtual.flags.Flags.virtualCamera()) {
+ return DEVICE_POLICY_DEFAULT;
+ }
+
+ if (mVirtualDeviceManager == null) {
+ mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class);
+ }
+ return mVirtualDeviceManager.getDevicePolicy(context.getDeviceId(), POLICY_TYPE_CAMERA);
+ }
+
// TODO(b/147726300): Investigate how to support foldables/multi-display devices.
private Size getDisplaySize() {
Size ret = new Size(0, 0);
@@ -566,7 +593,8 @@
CameraMetadataNative physicalCameraInfo =
cameraService.getCameraCharacteristics(physicalCameraId,
mContext.getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false);
+ /*overrideToPortrait*/ false, DEVICE_ID_DEFAULT,
+ DEVICE_POLICY_DEFAULT);
StreamConfiguration[] configs = physicalCameraInfo.get(
CameraCharacteristics.
SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
@@ -637,7 +665,7 @@
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId,
boolean overrideToPortrait) throws CameraAccessException {
- CameraCharacteristics characteristics = null;
+ CameraCharacteristics characteristics;
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
@@ -651,7 +679,8 @@
Size displaySize = getDisplaySize();
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
- mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
+ mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait,
+ mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
try {
info.setCameraId(Integer.parseInt(cameraId));
} catch (NumberFormatException e) {
@@ -659,7 +688,8 @@
}
boolean hasConcurrentStreams =
- CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
+ CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId,
+ mContext.getDeviceId());
info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
info.setDisplaySize(displaySize);
@@ -759,7 +789,6 @@
}
return getCameraDeviceSetupUnsafe(cameraId);
-
}
/**
@@ -806,7 +835,8 @@
}
if (CameraManagerGlobal.sCameraServiceDisabled
- || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList()).contains(cameraId)) {
+ || !Arrays.asList(CameraManagerGlobal.get().getCameraIdList(mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext))).contains(cameraId)) {
throw new IllegalArgumentException(
"Camera ID '" + cameraId + "' not available on device.");
}
@@ -846,7 +876,6 @@
Map<String, CameraCharacteristics> physicalIdsToChars =
getPhysicalIdToCharsMap(characteristics);
synchronized (mLock) {
-
ICameraDeviceUser cameraUser = null;
CameraDevice.CameraDeviceSetup cameraDeviceSetup = null;
if (Flags.cameraDeviceSetup()
@@ -855,7 +884,7 @@
}
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
- new android.hardware.camera2.impl.CameraDeviceImpl(
+ new CameraDeviceImpl(
cameraId,
callback,
executor,
@@ -876,7 +905,8 @@
cameraUser = cameraService.connectDevice(callbacks, cameraId,
mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
- overrideToPortrait);
+ overrideToPortrait, mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1002,7 +1032,6 @@
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {
-
openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
USE_CALLING_UID);
}
@@ -1052,6 +1081,12 @@
* {@link java.util.concurrent.Executor} as an argument instead of
* {@link android.os.Handler}.</p>
*
+ * <p>Do note that typically callbacks are expected to be dispatched
+ * by the executor in a single thread. If the executor uses two or
+ * more threads to dispatch callbacks, then clients must ensure correct
+ * synchronization and must also be able to handle potentially different
+ * ordering of the incoming callbacks.</p>
+ *
* @param cameraId
* The unique identifier of the camera device to open
* @param executor
@@ -1253,7 +1288,8 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
- CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
+ CameraManagerGlobal.get().setTorchMode(cameraId, enabled, mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -1316,7 +1352,8 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No camera available on device");
}
- CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength);
+ CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength,
+ mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
}
/**
@@ -1342,7 +1379,8 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No camera available on device.");
}
- return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
+ return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId, mContext.getDeviceId(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -1407,6 +1445,9 @@
*/
public static abstract class AvailabilityCallback {
+ private int mDeviceId;
+ private int mDevicePolicy;
+
/**
* A new camera has become available to use.
*
@@ -1645,6 +1686,10 @@
* @see #registerTorchCallback
*/
public static abstract class TorchCallback {
+
+ private int mDeviceId;
+ private int mDevicePolicy;
+
/**
* A camera's torch mode has become unavailable to set via {@link #setTorchMode}.
*
@@ -1867,6 +1912,10 @@
implements IBinder.DeathRecipient {
private static final String TAG = "CameraManagerGlobal";
+
+ private static final String BACK_CAMERA_ID = "0";
+ private static final String FRONT_CAMERA_ID = "1";
+
private final boolean DEBUG = false;
private final int CAMERA_SERVICE_RECONNECT_DELAY_MS = 1000;
@@ -1882,29 +1931,26 @@
private final ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1);
// Camera ID -> Status map
- private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
+ private final ArrayMap<DeviceCameraInfo, Integer> mDeviceStatus = new ArrayMap<>();
// Camera ID -> (physical camera ID -> Status map)
- private final ArrayMap<String, ArrayList<String>> mUnavailablePhysicalDevices =
- new ArrayMap<String, ArrayList<String>>();
+ private final ArrayMap<DeviceCameraInfo, ArrayList<String>> mUnavailablePhysicalDevices =
+ new ArrayMap<>();
// Opened Camera ID -> apk name map
- private final ArrayMap<String, String> mOpenedDevices = new ArrayMap<String, String>();
+ private final ArrayMap<DeviceCameraInfo, String> mOpenedDevices = new ArrayMap<>();
- private final Set<Set<String>> mConcurrentCameraIdCombinations =
- new ArraySet<Set<String>>();
+ private final Set<Set<String>> mConcurrentCameraIdCombinations = new ArraySet<>();
// Registered availability callbacks and their executors
- private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap =
- new ArrayMap<AvailabilityCallback, Executor>();
+ private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<>();
// torch client binder to set the torch mode with.
- private Binder mTorchClientBinder = new Binder();
+ private final Binder mTorchClientBinder = new Binder();
// Camera ID -> Torch status map
- private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>();
+ private final ArrayMap<DeviceCameraInfo, Integer> mTorchStatus = new ArrayMap<>();
// Registered torch callbacks and their executors
- private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap =
- new ArrayMap<TorchCallback, Executor>();
+ private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap = new ArrayMap<>();
private final Object mLock = new Object();
@@ -2019,25 +2065,28 @@
try {
CameraStatus[] cameraStatuses = cameraService.addListener(this);
- for (CameraStatus c : cameraStatuses) {
- onStatusChangedLocked(c.status, c.cameraId);
+ for (CameraStatus cameraStatus : cameraStatuses) {
+ DeviceCameraInfo info = new DeviceCameraInfo(cameraStatus.cameraId,
+ cameraStatus.deviceId);
+ onStatusChangedLocked(cameraStatus.status, info);
- if (c.unavailablePhysicalCameras != null) {
- for (String unavailPhysicalCamera : c.unavailablePhysicalCameras) {
+ if (cameraStatus.unavailablePhysicalCameras != null) {
+ for (String unavailablePhysicalCamera :
+ cameraStatus.unavailablePhysicalCameras) {
onPhysicalCameraStatusChangedLocked(
ICameraServiceListener.STATUS_NOT_PRESENT,
- c.cameraId, unavailPhysicalCamera);
+ info, unavailablePhysicalCamera);
}
}
- if (mHasOpenCloseListenerPermission &&
- c.status == ICameraServiceListener.STATUS_NOT_AVAILABLE &&
- !c.clientPackage.isEmpty()) {
- onCameraOpenedLocked(c.cameraId, c.clientPackage);
+ if (mHasOpenCloseListenerPermission
+ && cameraStatus.status == ICameraServiceListener.STATUS_NOT_AVAILABLE
+ && !cameraStatus.clientPackage.isEmpty()) {
+ onCameraOpenedLocked(info, cameraStatus.clientPackage);
}
}
mCameraService = cameraService;
- } catch(ServiceSpecificException e) {
+ } catch (ServiceSpecificException e) {
// Unexpected failure
throw new IllegalStateException("Failed to register a camera service listener", e);
} catch (RemoteException e) {
@@ -2118,36 +2167,32 @@
}
}
- private String[] extractCameraIdListLocked() {
- String[] cameraIds = null;
- int idCount = 0;
+ private String[] extractCameraIdListLocked(int deviceId, int devicePolicy) {
+ List<String> cameraIds = new ArrayList<>();
for (int i = 0; i < mDeviceStatus.size(); i++) {
int status = mDeviceStatus.valueAt(i);
+ DeviceCameraInfo info = mDeviceStatus.keyAt(i);
if (status == ICameraServiceListener.STATUS_NOT_PRESENT
- || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
- idCount++;
+ || status == ICameraServiceListener.STATUS_ENUMERATING
+ || shouldHideCamera(deviceId, devicePolicy, info)) {
+ continue;
+ }
+ cameraIds.add(info.mCameraId);
}
- cameraIds = new String[idCount];
- idCount = 0;
- for (int i = 0; i < mDeviceStatus.size(); i++) {
- int status = mDeviceStatus.valueAt(i);
- if (status == ICameraServiceListener.STATUS_NOT_PRESENT
- || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
- cameraIds[idCount] = mDeviceStatus.keyAt(i);
- idCount++;
- }
- return cameraIds;
+ return cameraIds.toArray(new String[0]);
}
private Set<Set<String>> extractConcurrentCameraIdListLocked() {
- Set<Set<String>> concurrentCameraIds = new ArraySet<Set<String>>();
+ Set<Set<String>> concurrentCameraIds = new ArraySet<>();
for (Set<String> cameraIds : mConcurrentCameraIdCombinations) {
- Set<String> extractedCameraIds = new ArraySet<String>();
+ Set<String> extractedCameraIds = new ArraySet<>();
for (String cameraId : cameraIds) {
+ // TODO(b/291736219): This to be made device-aware.
+ DeviceCameraInfo info = new DeviceCameraInfo(cameraId, DEVICE_ID_DEFAULT);
// if the camera id status is NOT_PRESENT or ENUMERATING; skip the device.
// TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed
// in the callback anyway.
- Integer status = mDeviceStatus.get(cameraId);
+ Integer status = mDeviceStatus.get(info);
if (status == null) {
// camera id not present
continue;
@@ -2194,19 +2239,39 @@
return s1.compareTo(s2);
}
}});
-
}
- public static boolean cameraStatusesContains(CameraStatus[] cameraStatuses, String id) {
+ private boolean shouldHideCamera(int currentDeviceId, int devicePolicy,
+ DeviceCameraInfo info) {
+ if (!android.companion.virtualdevice.flags.Flags.cameraDeviceAwareness()) {
+ // Don't hide any cameras if the device-awareness feature flag is disabled.
+ return false;
+ }
+
+ if (devicePolicy == DEVICE_POLICY_DEFAULT && info.mDeviceId == DEVICE_ID_DEFAULT) {
+ // Don't hide default-device cameras for a default-policy virtual device.
+ return false;
+ }
+
+ // External cameras should never be hidden.
+ if (!info.mCameraId.equals(FRONT_CAMERA_ID) && !info.mCameraId.equals(BACK_CAMERA_ID)) {
+ return false;
+ }
+
+ return currentDeviceId != info.mDeviceId;
+ }
+
+ private static boolean cameraStatusesContains(CameraStatus[] cameraStatuses,
+ DeviceCameraInfo info) {
for (CameraStatus c : cameraStatuses) {
- if (c.cameraId.equals(id)) {
+ if (c.cameraId.equals(info.mCameraId) && c.deviceId == info.mDeviceId) {
return true;
}
}
return false;
}
- public String[] getCameraIdListNoLazy() {
+ public String[] getCameraIdListNoLazy(int deviceId, int devicePolicy) {
if (sCameraServiceDisabled) {
return new String[] {};
}
@@ -2214,30 +2279,32 @@
CameraStatus[] cameraStatuses;
ICameraServiceListener.Stub testListener = new ICameraServiceListener.Stub() {
@Override
- public void onStatusChanged(int status, String id) throws RemoteException {
+ public void onStatusChanged(int status, String id, int deviceId)
+ throws RemoteException {
}
@Override
public void onPhysicalCameraStatusChanged(int status,
- String id, String physicalId) throws RemoteException {
+ String id, String physicalId, int deviceId) throws RemoteException {
}
@Override
- public void onTorchStatusChanged(int status, String id) throws RemoteException {
- }
- @Override
- public void onTorchStrengthLevelChanged(String id, int newStrengthLevel)
+ public void onTorchStatusChanged(int status, String id, int deviceId)
throws RemoteException {
}
@Override
+ public void onTorchStrengthLevelChanged(String id, int newStrengthLevel,
+ int deviceId) throws RemoteException {
+ }
+ @Override
public void onCameraAccessPrioritiesChanged() {
}
@Override
- public void onCameraOpened(String id, String clientPackageId) {
+ public void onCameraOpened(String id, String clientPackageId, int deviceId) {
}
@Override
- public void onCameraClosed(String id) {
+ public void onCameraClosed(String id, int deviceId) {
}};
- String[] cameraIds = null;
+ String[] cameraIds;
synchronized (mLock) {
connectCameraServiceLocked();
try {
@@ -2255,23 +2322,24 @@
// devices removed as well. This is the same situation.
cameraStatuses = mCameraService.addListener(testListener);
mCameraService.removeListener(testListener);
- for (CameraStatus c : cameraStatuses) {
- onStatusChangedLocked(c.status, c.cameraId);
+ for (CameraStatus cameraStatus : cameraStatuses) {
+ onStatusChangedLocked(cameraStatus.status,
+ new DeviceCameraInfo(cameraStatus.cameraId, cameraStatus.deviceId));
}
- Set<String> deviceCameraIds = mDeviceStatus.keySet();
- ArrayList<String> deviceIdsToRemove = new ArrayList<String>();
- for (String deviceCameraId : deviceCameraIds) {
+ Set<DeviceCameraInfo> deviceCameraInfos = mDeviceStatus.keySet();
+ List<DeviceCameraInfo> deviceInfosToRemove = new ArrayList<>();
+ for (DeviceCameraInfo info : deviceCameraInfos) {
// Its possible that a device id was removed without a callback notifying
// us. This may happen in case a process 'drops' system camera permissions
// (even though the permission isn't a changeable one, tests may call
// adoptShellPermissionIdentity() and then dropShellPermissionIdentity().
- if (!cameraStatusesContains(cameraStatuses, deviceCameraId)) {
- deviceIdsToRemove.add(deviceCameraId);
+ if (!cameraStatusesContains(cameraStatuses, info)) {
+ deviceInfosToRemove.add(info);
}
}
- for (String id : deviceIdsToRemove) {
- onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, id);
- mTorchStatus.remove(id);
+ for (DeviceCameraInfo info : deviceInfosToRemove) {
+ onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, info);
+ mTorchStatus.remove(info);
}
} catch (ServiceSpecificException e) {
// Unexpected failure
@@ -2280,7 +2348,7 @@
} catch (RemoteException e) {
// Camera service is now down, leave mCameraService as null
}
- cameraIds = extractCameraIdListLocked();
+ cameraIds = extractCameraIdListLocked(deviceId, devicePolicy);
}
sortCameraIds(cameraIds);
return cameraIds;
@@ -2290,19 +2358,19 @@
* Get a list of all camera IDs that are at least PRESENT; ignore devices that are
* NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
*/
- public String[] getCameraIdList() {
- String[] cameraIds = null;
+ public String[] getCameraIdList(int deviceId, int devicePolicy) {
+ String[] cameraIds;
synchronized (mLock) {
// Try to make sure we have an up-to-date list of camera devices.
connectCameraServiceLocked();
- cameraIds = extractCameraIdListLocked();
+ cameraIds = extractCameraIdListLocked(deviceId, devicePolicy);
}
sortCameraIds(cameraIds);
return cameraIds;
}
public @NonNull Set<Set<String>> getConcurrentCameraIds() {
- Set<Set<String>> concurrentStreamingCameraIds = null;
+ Set<Set<String>> concurrentStreamingCameraIds;
synchronized (mLock) {
// Try to make sure we have an up-to-date list of concurrent camera devices.
connectCameraServiceLocked();
@@ -2315,11 +2383,12 @@
public boolean isConcurrentSessionConfigurationSupported(
@NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations,
int targetSdkVersion) throws CameraAccessException {
-
if (cameraIdsAndSessionConfigurations == null) {
throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null");
}
+ // TODO(b/291736219): Check if this API needs to be made device-aware.
+
int size = cameraIdsAndSessionConfigurations.size();
if (size == 0) {
throw new IllegalArgumentException("camera id and session combination is empty");
@@ -2361,19 +2430,21 @@
}
}
- /**
- * Helper function to find out if a camera id is in the set of combinations returned by
- * getConcurrentCameraIds()
- * @param cameraId the unique identifier of the camera device to query
- * @return Whether the camera device was found in the set of combinations returned by
- * getConcurrentCameraIds
- */
- public boolean cameraIdHasConcurrentStreamsLocked(String cameraId) {
- if (!mDeviceStatus.containsKey(cameraId)) {
+ /**
+ * Helper function to find out if a camera id is in the set of combinations returned by
+ * getConcurrentCameraIds()
+ * @param cameraId the unique identifier of the camera device to query
+ * @param deviceId the device id of the context
+ * @return Whether the camera device was found in the set of combinations returned by
+ * getConcurrentCameraIds
+ */
+ public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId) {
+ DeviceCameraInfo info = new DeviceCameraInfo(cameraId, deviceId);
+ if (!mDeviceStatus.containsKey(info)) {
// physical camera ids aren't advertised in concurrent camera id combinations.
if (DEBUG) {
Log.v(TAG, " physical camera id " + cameraId + " is hidden." +
- " Available logical camera ids : " + mDeviceStatus.toString());
+ " Available logical camera ids : " + mDeviceStatus);
}
return false;
}
@@ -2385,9 +2456,9 @@
return false;
}
- public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
- synchronized(mLock) {
-
+ public void setTorchMode(String cameraId, boolean enabled, int deviceId, int devicePolicy)
+ throws CameraAccessException {
+ synchronized (mLock) {
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
}
@@ -2399,7 +2470,8 @@
}
try {
- cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
+ cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder, deviceId,
+ devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2409,10 +2481,10 @@
}
}
- public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength) throws
- CameraAccessException {
- synchronized(mLock) {
-
+ public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength, int deviceId,
+ int devicePolicy)
+ throws CameraAccessException {
+ synchronized (mLock) {
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
}
@@ -2425,7 +2497,7 @@
try {
cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength,
- mTorchClientBinder);
+ mTorchClientBinder, deviceId, devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2435,9 +2507,10 @@
}
}
- public int getTorchStrengthLevel(String cameraId) throws CameraAccessException {
- int torchStrength = 0;
- synchronized(mLock) {
+ public int getTorchStrengthLevel(String cameraId, int deviceId, int devicePolicy)
+ throws CameraAccessException {
+ int torchStrength;
+ synchronized (mLock) {
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
}
@@ -2449,7 +2522,8 @@
}
try {
- torchStrength = cameraService.getTorchStrengthLevel(cameraId);
+ torchStrength = cameraService.getTorchStrengthLevel(cameraId, deviceId,
+ devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2506,13 +2580,7 @@
final Executor executor) {
final long ident = Binder.clearCallingIdentity();
try {
- executor.execute(
- new Runnable() {
- @Override
- public void run() {
- callback.onCameraAccessPrioritiesChanged();
- }
- });
+ executor.execute(callback::onCameraAccessPrioritiesChanged);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2522,13 +2590,7 @@
final Executor executor, final String id, final String packageId) {
final long ident = Binder.clearCallingIdentity();
try {
- executor.execute(
- new Runnable() {
- @Override
- public void run() {
- callback.onCameraOpened(id, packageId);
- }
- });
+ executor.execute(() -> callback.onCameraOpened(id, packageId));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2538,13 +2600,7 @@
final Executor executor, final String id) {
final long ident = Binder.clearCallingIdentity();
try {
- executor.execute(
- new Runnable() {
- @Override
- public void run() {
- callback.onCameraClosed(id);
- }
- });
+ executor.execute(() -> callback.onCameraClosed(id));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2556,16 +2612,13 @@
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(
- new Runnable() {
- @Override
- public void run() {
+ () -> {
if (physicalId == null) {
callback.onCameraAvailable(id);
} else {
callback.onPhysicalCameraAvailable(id, physicalId);
}
- }
- });
+ });
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2573,16 +2626,13 @@
final long ident = Binder.clearCallingIdentity();
try {
executor.execute(
- new Runnable() {
- @Override
- public void run() {
+ () -> {
if (physicalId == null) {
callback.onCameraUnavailable(id);
} else {
callback.onPhysicalCameraUnavailable(id, physicalId);
}
- }
- });
+ });
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2594,28 +2644,24 @@
switch(status) {
case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: {
- final long ident = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> {
- callback.onTorchModeChanged(id, status ==
- ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON);
- });
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onTorchModeChanged(id, status
+ == ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
break;
+ }
default: {
- final long ident = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> {
- callback.onTorchModeUnavailable(id);
- });
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onTorchModeUnavailable(id));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
break;
+ }
}
}
@@ -2623,9 +2669,7 @@
final Executor executor, final String id, final int newStrengthLevel) {
final long ident = Binder.clearCallingIdentity();
try {
- executor.execute(() -> {
- callback.onTorchStrengthLevelChanged(id, newStrengthLevel);
- });
+ executor.execute(() -> callback.onTorchStrengthLevelChanged(id, newStrengthLevel));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2637,48 +2681,57 @@
*/
private void updateCallbackLocked(AvailabilityCallback callback, Executor executor) {
for (int i = 0; i < mDeviceStatus.size(); i++) {
- String id = mDeviceStatus.keyAt(i);
+ DeviceCameraInfo info = mDeviceStatus.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
+
Integer status = mDeviceStatus.valueAt(i);
- postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
+ postSingleUpdate(callback, executor, info.mCameraId, null /* physicalId */, status);
// Send the NOT_PRESENT state for unavailable physical cameras
if ((isAvailable(status) || physicalCallbacksAreEnabledForUnavailableCamera())
- && mUnavailablePhysicalDevices.containsKey(id)) {
- ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
+ && mUnavailablePhysicalDevices.containsKey(info)) {
+ List<String> unavailableIds = mUnavailablePhysicalDevices.get(info);
for (String unavailableId : unavailableIds) {
- postSingleUpdate(callback, executor, id, unavailableId,
+ postSingleUpdate(callback, executor, info.mCameraId, unavailableId,
ICameraServiceListener.STATUS_NOT_PRESENT);
}
}
-
}
+
for (int i = 0; i < mOpenedDevices.size(); i++) {
- String id = mOpenedDevices.keyAt(i);
+ DeviceCameraInfo info = mOpenedDevices.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
+
String clientPackageId = mOpenedDevices.valueAt(i);
- postSingleCameraOpenedUpdate(callback, executor, id, clientPackageId);
+ postSingleCameraOpenedUpdate(callback, executor, info.mCameraId, clientPackageId);
}
}
- private void onStatusChangedLocked(int status, String id) {
+ private void onStatusChangedLocked(int status, DeviceCameraInfo info) {
if (DEBUG) {
Log.v(TAG,
- String.format("Camera id %s has status changed to 0x%x", id, status));
+ String.format("Camera id %s has status changed to 0x%x for device %d",
+ info.mCameraId, status, info.mDeviceId));
}
if (!validStatus(status)) {
- Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id,
- status));
+ Log.e(TAG, String.format("Ignoring invalid camera %s status 0x%x for device %d",
+ info.mCameraId, status, info.mDeviceId));
return;
}
Integer oldStatus;
if (status == ICameraServiceListener.STATUS_NOT_PRESENT) {
- oldStatus = mDeviceStatus.remove(id);
- mUnavailablePhysicalDevices.remove(id);
+ oldStatus = mDeviceStatus.remove(info);
+ mUnavailablePhysicalDevices.remove(info);
} else {
- oldStatus = mDeviceStatus.put(id, status);
+ oldStatus = mDeviceStatus.put(info, status);
if (oldStatus == null) {
- mUnavailablePhysicalDevices.put(id, new ArrayList<String>());
+ mUnavailablePhysicalDevices.put(info, new ArrayList<>());
}
}
@@ -2718,45 +2771,50 @@
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
- postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
+ final Executor executor = mCallbackMap.valueAt(i);
+ postSingleUpdate(callback, executor, info.mCameraId, null /* physicalId */, status);
// Send the NOT_PRESENT state for unavailable physical cameras
- if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
- ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
+ if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(info)) {
+ List<String> unavailableIds = mUnavailablePhysicalDevices.get(info);
for (String unavailableId : unavailableIds) {
- postSingleUpdate(callback, executor, id, unavailableId,
+ postSingleUpdate(callback, executor, info.mCameraId, unavailableId,
ICameraServiceListener.STATUS_NOT_PRESENT);
}
}
}
} // onStatusChangedLocked
- private void onPhysicalCameraStatusChangedLocked(int status,
- String id, String physicalId) {
+ private void onPhysicalCameraStatusChangedLocked(int status, DeviceCameraInfo info,
+ String physicalId) {
if (DEBUG) {
Log.v(TAG,
- String.format("Camera id %s physical camera id %s has status "
- + "changed to 0x%x", id, physicalId, status));
+ String.format("Camera id %s physical camera id %s has status changed "
+ + "to 0x%x for device %d", info.mCameraId, physicalId, status,
+ info.mDeviceId));
}
if (!validStatus(status)) {
Log.e(TAG, String.format(
- "Ignoring invalid device %s physical device %s status 0x%x", id,
- physicalId, status));
+ "Ignoring invalid device %s physical device %s status 0x%x for device %d",
+ info.mCameraId, physicalId, status, info.mDeviceId));
return;
}
//TODO: Do we need to treat this as error?
- if (!mDeviceStatus.containsKey(id) || !mUnavailablePhysicalDevices.containsKey(id)) {
+ if (!mDeviceStatus.containsKey(info)
+ || !mUnavailablePhysicalDevices.containsKey(info)) {
Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera "
- + "status change", id));
+ + "status change", info.mCameraId));
return;
}
- ArrayList<String> unavailablePhysicalDevices = mUnavailablePhysicalDevices.get(id);
+ List<String> unavailablePhysicalDevices = mUnavailablePhysicalDevices.get(info);
if (!isAvailable(status)
&& !unavailablePhysicalDevices.contains(physicalId)) {
unavailablePhysicalDevices.add(physicalId);
@@ -2777,42 +2835,51 @@
}
if (!physicalCallbacksAreEnabledForUnavailableCamera()
- && !isAvailable(mDeviceStatus.get(id))) {
+ && !isAvailable(mDeviceStatus.get(info))) {
Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
- + "status change callback(s)", id));
+ + "status change callback(s)", info.mCameraId));
return;
}
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
- postSingleUpdate(callback, executor, id, physicalId, status);
+ final Executor executor = mCallbackMap.valueAt(i);
+ postSingleUpdate(callback, executor, info.mCameraId, physicalId, status);
}
} // onPhysicalCameraStatusChangedLocked
private void updateTorchCallbackLocked(TorchCallback callback, Executor executor) {
for (int i = 0; i < mTorchStatus.size(); i++) {
- String id = mTorchStatus.keyAt(i);
+ DeviceCameraInfo info = mTorchStatus.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
+
Integer status = mTorchStatus.valueAt(i);
- postSingleTorchUpdate(callback, executor, id, status);
+ postSingleTorchUpdate(callback, executor, info.mCameraId, status);
}
}
- private void onTorchStatusChangedLocked(int status, String id) {
+ private void onTorchStatusChangedLocked(int status, DeviceCameraInfo info) {
if (DEBUG) {
- Log.v(TAG,
- String.format("Camera id %s has torch status changed to 0x%x", id, status));
+ Log.v(TAG, String.format(
+ "Camera id %s has torch status changed to 0x%x for device %d",
+ info.mCameraId, status, info.mDeviceId));
}
if (!validTorchStatus(status)) {
- Log.e(TAG, String.format("Ignoring invalid device %s torch status 0x%x", id,
- status));
+ Log.e(TAG, String.format(
+ "Ignoring invalid camera %s torch status 0x%x for device %d",
+ info.mCameraId, status, info.mDeviceId));
return;
}
- Integer oldStatus = mTorchStatus.put(id, status);
+ Integer oldStatus = mTorchStatus.put(info, status);
if (oldStatus != null && oldStatus == status) {
if (DEBUG) {
Log.v(TAG, String.format(
@@ -2824,25 +2891,34 @@
final int callbackCount = mTorchCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- final Executor executor = mTorchCallbackMap.valueAt(i);
final TorchCallback callback = mTorchCallbackMap.keyAt(i);
- postSingleTorchUpdate(callback, executor, id, status);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
+
+ final Executor executor = mTorchCallbackMap.valueAt(i);
+ postSingleTorchUpdate(callback, executor, info.mCameraId, status);
}
} // onTorchStatusChangedLocked
- private void onTorchStrengthLevelChangedLocked(String cameraId, int newStrengthLevel) {
+ private void onTorchStrengthLevelChangedLocked(DeviceCameraInfo info,
+ int newStrengthLevel) {
if (DEBUG) {
-
- Log.v(TAG,
- String.format("Camera id %s has torch strength level changed to %d",
- cameraId, newStrengthLevel));
+ Log.v(TAG, String.format(
+ "Camera id %s has torch strength level changed to %d for device %d",
+ info.mCameraId, newStrengthLevel, info.mDeviceId));
}
final int callbackCount = mTorchCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- final Executor executor = mTorchCallbackMap.valueAt(i);
final TorchCallback callback = mTorchCallbackMap.keyAt(i);
- postSingleTorchStrengthLevelUpdate(callback, executor, cameraId, newStrengthLevel);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
+
+ final Executor executor = mTorchCallbackMap.valueAt(i);
+ postSingleTorchStrengthLevelUpdate(callback, executor, info.mCameraId,
+ newStrengthLevel);
}
} // onTorchStrengthLevelChanged
@@ -2856,13 +2932,16 @@
* onCameraOpened/onCameraClosed callback
*/
public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor,
- boolean hasOpenCloseListenerPermission) {
+ boolean hasOpenCloseListenerPermission, int deviceId, int devicePolicy) {
synchronized (mLock) {
// In practice, this permission doesn't change. So we don't need one flag for each
// callback object.
mHasOpenCloseListenerPermission = hasOpenCloseListenerPermission;
connectCameraServiceLocked();
+ callback.mDeviceId = deviceId;
+ callback.mDevicePolicy = devicePolicy;
+
Executor oldExecutor = mCallbackMap.put(callback, executor);
// For new callbacks, provide initial availability information
if (oldExecutor == null) {
@@ -2888,10 +2967,14 @@
}
}
- public void registerTorchCallback(TorchCallback callback, Executor executor) {
+ public void registerTorchCallback(TorchCallback callback, Executor executor, int deviceId,
+ int devicePolicy) {
synchronized(mLock) {
connectCameraServiceLocked();
+ callback.mDeviceId = deviceId;
+ callback.mDevicePolicy = devicePolicy;
+
Executor oldExecutor = mTorchCallbackMap.put(callback, executor);
// For new callbacks, provide initial torch information
if (oldExecutor == null) {
@@ -2915,32 +2998,36 @@
* Callback from camera service notifying the process about camera availability changes
*/
@Override
- public void onStatusChanged(int status, String cameraId) throws RemoteException {
+ public void onStatusChanged(int status, String cameraId, int deviceId)
+ throws RemoteException {
synchronized(mLock) {
- onStatusChangedLocked(status, cameraId);
+ onStatusChangedLocked(status, new DeviceCameraInfo(cameraId, deviceId));
}
}
@Override
public void onPhysicalCameraStatusChanged(int status, String cameraId,
- String physicalCameraId) throws RemoteException {
+ String physicalCameraId, int deviceId) throws RemoteException {
synchronized (mLock) {
- onPhysicalCameraStatusChangedLocked(status, cameraId, physicalCameraId);
+ onPhysicalCameraStatusChangedLocked(status,
+ new DeviceCameraInfo(cameraId, deviceId), physicalCameraId);
}
}
@Override
- public void onTorchStatusChanged(int status, String cameraId) throws RemoteException {
- synchronized (mLock) {
- onTorchStatusChangedLocked(status, cameraId);
- }
- }
-
- @Override
- public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel)
+ public void onTorchStatusChanged(int status, String cameraId, int deviceId)
throws RemoteException {
synchronized (mLock) {
- onTorchStrengthLevelChangedLocked(cameraId, newStrengthLevel);
+ onTorchStatusChangedLocked(status, new DeviceCameraInfo(cameraId, deviceId));
+ }
+ }
+
+ @Override
+ public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel, int deviceId)
+ throws RemoteException {
+ synchronized (mLock) {
+ onTorchStrengthLevelChangedLocked(new DeviceCameraInfo(cameraId, deviceId),
+ newStrengthLevel);
}
}
@@ -2958,14 +3045,14 @@
}
@Override
- public void onCameraOpened(String cameraId, String clientPackageId) {
+ public void onCameraOpened(String cameraId, String clientPackageId, int deviceId) {
synchronized (mLock) {
- onCameraOpenedLocked(cameraId, clientPackageId);
+ onCameraOpenedLocked(new DeviceCameraInfo(cameraId, deviceId), clientPackageId);
}
}
- private void onCameraOpenedLocked(String cameraId, String clientPackageId) {
- String oldApk = mOpenedDevices.put(cameraId, clientPackageId);
+ private void onCameraOpenedLocked(DeviceCameraInfo info, String clientPackageId) {
+ String oldApk = mOpenedDevices.put(info, clientPackageId);
if (oldApk != null) {
if (oldApk.equals(clientPackageId)) {
@@ -2984,29 +3071,35 @@
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
- postSingleCameraOpenedUpdate(callback, executor, cameraId, clientPackageId);
+ final Executor executor = mCallbackMap.valueAt(i);
+ postSingleCameraOpenedUpdate(callback, executor, info.mCameraId, clientPackageId);
}
}
@Override
- public void onCameraClosed(String cameraId) {
+ public void onCameraClosed(String cameraId, int deviceId) {
synchronized (mLock) {
- onCameraClosedLocked(cameraId);
+ onCameraClosedLocked(new DeviceCameraInfo(cameraId, deviceId));
}
}
- private void onCameraClosedLocked(String cameraId) {
- mOpenedDevices.remove(cameraId);
+ private void onCameraClosedLocked(DeviceCameraInfo info) {
+ mOpenedDevices.remove(info);
final int callbackCount = mCallbackMap.size();
for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ if (shouldHideCamera(callback.mDeviceId, callback.mDevicePolicy, info)) {
+ continue;
+ }
- postSingleCameraClosedUpdate(callback, executor, cameraId);
+ final Executor executor = mCallbackMap.valueAt(i);
+ postSingleCameraClosedUpdate(callback, executor, info.mCameraId);
}
}
@@ -3062,17 +3155,18 @@
// Iterate from the end to the beginning because onStatusChangedLocked removes
// entries from the ArrayMap.
for (int i = mDeviceStatus.size() - 1; i >= 0; i--) {
- String cameraId = mDeviceStatus.keyAt(i);
- onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
+ DeviceCameraInfo info = mDeviceStatus.keyAt(i);
+ onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, info);
if (mHasOpenCloseListenerPermission) {
- onCameraClosedLocked(cameraId);
+ onCameraClosedLocked(info);
}
}
+
for (int i = 0; i < mTorchStatus.size(); i++) {
- String cameraId = mTorchStatus.keyAt(i);
+ DeviceCameraInfo info = mTorchStatus.keyAt(i);
onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE,
- cameraId);
+ info);
}
mConcurrentCameraIdCombinations.clear();
@@ -3081,6 +3175,31 @@
}
}
- } // CameraManagerGlobal
+ private static final class DeviceCameraInfo {
+ private final String mCameraId;
+ private final int mDeviceId;
+ DeviceCameraInfo(String cameraId, int deviceId) {
+ mCameraId = cameraId;
+ mDeviceId = deviceId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DeviceCameraInfo that = (DeviceCameraInfo) o;
+ return mDeviceId == that.mDeviceId && Objects.equals(mCameraId, that.mCameraId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCameraId, mDeviceId);
+ }
+ }
+ } // CameraManagerGlobal
} // CameraManager
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 97c03ed..81bb9ac 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -290,6 +290,55 @@
}
};
+ private class ClientStateCallback extends StateCallback {
+ private final Executor mClientExecutor;
+ private final StateCallback mClientStateCallback;
+
+ private ClientStateCallback(@NonNull Executor clientExecutor,
+ @NonNull StateCallback clientStateCallback) {
+ mClientExecutor = clientExecutor;
+ mClientStateCallback = clientStateCallback;
+ }
+
+ public void onClosed(@NonNull CameraDevice camera) {
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onClosed(camera);
+ }
+ });
+ }
+ @Override
+ public void onOpened(@NonNull CameraDevice camera) {
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onOpened(camera);
+ }
+ });
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice camera) {
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onDisconnected(camera);
+ }
+ });
+ }
+
+ @Override
+ public void onError(@NonNull CameraDevice camera, int error) {
+ mClientExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mClientStateCallback.onError(camera, error);
+ }
+ });
+ }
+ }
+
public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
CameraCharacteristics characteristics,
Map<String, CameraCharacteristics> physicalIdsToChars,
@@ -300,8 +349,13 @@
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
- mDeviceCallback = callback;
- mDeviceExecutor = executor;
+ if (Flags.singleThreadExecutor()) {
+ mDeviceCallback = new ClientStateCallback(executor, callback);
+ mDeviceExecutor = Executors.newSingleThreadExecutor();
+ } else {
+ mDeviceCallback = callback;
+ mDeviceExecutor = executor;
+ }
mCharacteristics = characteristics;
mPhysicalIdsToChars = physicalIdsToChars;
mAppTargetSdkVersion = appTargetSdkVersion;
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index 9e01438..24ac0b5 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.hardware.camera2.impl;
import android.annotation.CallbackExecutor;
@@ -71,7 +72,8 @@
try {
CameraMetadataNative defaultRequest = cameraService.createDefaultRequest(mCameraId,
- templateType);
+ templateType, mContext.getDeviceId(),
+ mCameraManager.getDevicePolicyFromContext(mContext));
CameraDeviceImpl.disableZslIfNeeded(defaultRequest, mTargetSdkVersion,
templateType);
@@ -103,7 +105,8 @@
try {
return cameraService.isSessionConfigurationWithParametersSupported(
- mCameraId, config);
+ mCameraId, config, mContext.getDeviceId(),
+ mCameraManager.getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -131,7 +134,9 @@
try {
CameraMetadataNative metadataNative = cameraService.getSessionCharacteristics(
mCameraId, mTargetSdkVersion,
- CameraManager.shouldOverrideToPortrait(mContext), sessionConfig);
+ CameraManager.shouldOverrideToPortrait(mContext), sessionConfig,
+ mContext.getDeviceId(),
+ mCameraManager.getDevicePolicyFromContext(mContext));
return new CameraCharacteristics(metadataNative);
} catch (ServiceSpecificException e) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 689e343..64fc4c2 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -333,14 +333,12 @@
private final ArraySet<@PhysicalDeviceStateProperties Integer> mPhysicalProperties;
private Configuration(int identifier, @NonNull String name,
- @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties,
- @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+ @NonNull ArraySet<@SystemDeviceStateProperties Integer> systemProperties,
+ @NonNull ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties) {
mIdentifier = identifier;
mName = name;
- mSystemProperties = new ArraySet<@SystemDeviceStateProperties Integer>(
- systemProperties);
- mPhysicalProperties = new ArraySet<@PhysicalDeviceStateProperties Integer>(
- physicalProperties);
+ mSystemProperties = systemProperties;
+ mPhysicalProperties = physicalProperties;
}
/** Returns the unique identifier for the device state. */
@@ -479,8 +477,8 @@
*/
@NonNull
public DeviceState.Configuration build() {
- return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
- mPhysicalProperties);
+ return new DeviceState.Configuration(mIdentifier, mName,
+ new ArraySet<>(mSystemProperties), new ArraySet<>(mPhysicalProperties));
}
}
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java
index c319c89..28561ec 100644
--- a/core/java/android/hardware/devicestate/DeviceStateInfo.java
+++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java
@@ -25,7 +25,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -77,9 +76,11 @@
* NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
* supplied parameters.
*/
- public DeviceStateInfo(@NonNull List<DeviceState> supportedStates, DeviceState baseState,
+ // Using the specific types to avoid virtual method calls in binder transactions
+ @SuppressWarnings("NonApiType")
+ public DeviceStateInfo(@NonNull ArrayList<DeviceState> supportedStates, DeviceState baseState,
DeviceState state) {
- this.supportedStates = new ArrayList<>(supportedStates);
+ this.supportedStates = supportedStates;
this.baseState = baseState;
this.currentState = state;
}
@@ -89,13 +90,13 @@
* the fields of the returned instance.
*/
public DeviceStateInfo(@NonNull DeviceStateInfo info) {
- this(List.copyOf(info.supportedStates), info.baseState, info.currentState);
+ this(new ArrayList<>(info.supportedStates), info.baseState, info.currentState);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) return true;
- if (other == null || getClass() != other.getClass()) return false;
+ if (other == null || (getClass() != other.getClass())) return false;
DeviceStateInfo that = (DeviceStateInfo) other;
return baseState.equals(that.baseState)
&& currentState.equals(that.currentState)
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
index 6ef692f..1247168 100644
--- a/core/java/android/hardware/face/FaceSensorConfigurations.java
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -22,11 +22,12 @@
import android.content.Context;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import androidx.annotation.NonNull;
@@ -36,7 +37,6 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.function.Function;
/**
* Provides the sensor props for face sensor, if available.
@@ -74,22 +74,10 @@
/**
* Process AIDL instances to extract sensor props and add it to the sensor map.
* @param aidlInstances available face AIDL instances
- * @param getIFace function that provides the daemon for the specific instance
*/
- public void addAidlConfigs(@NonNull String[] aidlInstances,
- @NonNull Function<String, IFace> getIFace) {
+ public void addAidlConfigs(@NonNull String[] aidlInstances) {
for (String aidlInstance : aidlInstances) {
- final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
- IFace face = getIFace.apply(fqName);
- try {
- if (face != null) {
- mSensorPropsMap.put(aidlInstance, face.getSensorProps());
- } else {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
- }
- } catch (RemoteException e) {
- Log.d(TAG, "Unable to get sensor properties!");
- }
+ mSensorPropsMap.put(aidlInstance, null);
}
}
@@ -131,38 +119,31 @@
}
/**
- * Return sensor props for the given instance. If instance is not available,
- * then null is returned.
+ * Checks if {@param instance} exists.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
- if (mSensorPropsMap.containsKey(instance)) {
- return new Pair<>(instance, mSensorPropsMap.get(instance));
- }
-
- return null;
+ public boolean doesInstanceExist(String instance) {
+ return mSensorPropsMap.containsKey(instance);
}
/**
- * Return the first pair of instance and sensor props, which does not correspond to the given
- * If instance is not available, then null is returned.
+ * Return the first HAL instance, which does not correspond to the given {@param instance}.
+ * If another instance is not available, then null is returned.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ public String getSensorNameNotForInstance(String instance) {
Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
(instanceName) -> !instanceName.equals(instance)).findFirst();
- return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
- this::getSensorPair);
+ return notAVirtualInstance.orElse(null);
}
/**
- * Returns the first pair of instance and sensor props that has been added to the map.
+ * Returns the first instance that has been added to the map.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPair() {
+ public String getSensorInstance() {
Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
- return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
-
+ return optionalInstance.orElse(null);
}
public boolean getResetLockoutRequiresChallenge() {
@@ -179,4 +160,31 @@
dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
dest.writeMap(mSensorPropsMap);
}
+
+ /**
+ * Returns face sensor props for the HAL {@param instance}.
+ */
+ @Nullable
+ public SensorProps[] getSensorPropForInstance(String instance) {
+ SensorProps[] props = mSensorPropsMap.get(instance);
+
+ //Props should not be null for HIDL configs
+ if (props != null) {
+ return props;
+ }
+
+ final String fqName = IFace.DESCRIPTOR + "/" + instance;
+ IFace face = IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(fqName)));
+ try {
+ if (face != null) {
+ props = face.getSensorProps();
+ } else {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ return props;
+ }
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
index f214494a..43c0da9 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -23,18 +23,18 @@
import android.content.Context;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
-import android.util.Pair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.function.Function;
/**
* Provides the sensor props for fingerprint sensor, if available.
@@ -68,23 +68,10 @@
/**
* Process AIDL instances to extract sensor props and add it to the sensor map.
* @param aidlInstances available face AIDL instances
- * @param getIFingerprint function that provides the daemon for the specific instance
*/
- public void addAidlSensors(@NonNull String[] aidlInstances,
- @NonNull Function<String, IFingerprint> getIFingerprint) {
+ public void addAidlSensors(@NonNull String[] aidlInstances) {
for (String aidlInstance : aidlInstances) {
- try {
- final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
- final IFingerprint fp = getIFingerprint.apply(fqName);
- if (fp != null) {
- SensorProps[] props = fp.getSensorProps();
- mSensorPropsMap.put(aidlInstance, props);
- } else {
- Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
- }
- } catch (RemoteException e) {
- Log.d(TAG, "Unable to get sensor properties!");
- }
+ mSensorPropsMap.put(aidlInstance, null);
}
}
@@ -133,38 +120,31 @@
}
/**
- * Return sensor props for the given instance. If instance is not available,
- * then null is returned.
+ * Checks if {@param instance} exists.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
- if (mSensorPropsMap.containsKey(instance)) {
- return new Pair<>(instance, mSensorPropsMap.get(instance));
- }
-
- return null;
+ public boolean doesInstanceExist(String instance) {
+ return mSensorPropsMap.containsKey(instance);
}
/**
- * Return the first pair of instance and sensor props, which does not correspond to the given
- * If instance is not available, then null is returned.
+ * Return the first HAL instance, which does not correspond to the given {@param instance}.
+ * If another instance is not available, then null is returned.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ public String getSensorNameNotForInstance(String instance) {
Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
(instanceName) -> !instanceName.equals(instance)).findFirst();
- return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
- this::getSensorPair);
+ return notAVirtualInstance.orElse(null);
}
/**
- * Returns the first pair of instance and sensor props that has been added to the map.
+ * Returns the first instance that has been added to the map.
*/
@Nullable
- public Pair<String, SensorProps[]> getSensorPair() {
+ public String getSensorInstance() {
Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
- return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
-
+ return optionalInstance.orElse(null);
}
public boolean getResetLockoutRequiresHardwareAuthToken() {
@@ -181,4 +161,31 @@
dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
dest.writeMap(mSensorPropsMap);
}
+
+ /**
+ * Returns fingerprint sensor props for the HAL {@param instance}.
+ */
+ @Nullable
+ public SensorProps[] getSensorPropForInstance(String instance) {
+ SensorProps[] props = mSensorPropsMap.get(instance);
+
+ //Props should not be null for HIDL configs
+ if (props != null) {
+ return props;
+ }
+
+ try {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
+ final IFingerprint fp = IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(fqName)));
+ if (fp != null) {
+ props = fp.getSensorProps();
+ } else {
+ Log.d(TAG, "IFingerprint null for instance " + instance);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ return props;
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4dbdd91..2cd7aeb 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,6 +16,7 @@
package android.inputmethodservice;
+import static android.view.inputmethod.Flags.predictiveBackIme;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
@@ -3098,7 +3099,7 @@
cancelImeSurfaceRemoval();
mInShowWindow = false;
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- registerCompatOnBackInvokedCallback();
+ registerDefaultOnBackInvokedCallback();
}
@@ -3107,18 +3108,27 @@
* back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code
* around to handle back on older devices.
*/
- private void registerCompatOnBackInvokedCallback() {
+ private void registerDefaultOnBackInvokedCallback() {
if (mBackCallbackRegistered) {
return;
}
if (mWindow != null) {
- mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+ if (getApplicationInfo().isOnBackInvokedCallbackEnabled() && predictiveBackIme()) {
+ // Register the compat callback as system-callback if IME has opted in for
+ // predictive back (and predictiveBackIme feature flag is enabled). This indicates
+ // to the receiving process (application process) that a predictive IME dismiss
+ // animation may be played instead of invoking the callback.
+ mWindow.getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(
+ mCompatBackCallback);
+ } else {
+ mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+ }
mBackCallbackRegistered = true;
}
}
- private void unregisterCompatOnBackInvokedCallback() {
+ private void unregisterDefaultOnBackInvokedCallback() {
if (!mBackCallbackRegistered) {
return;
}
@@ -3251,7 +3261,7 @@
}
mLastWasInFullscreenMode = mIsFullscreen;
updateFullscreenMode();
- unregisterCompatOnBackInvokedCallback();
+ unregisterDefaultOnBackInvokedCallback();
}
/**
@@ -3328,7 +3338,7 @@
// Back callback is typically unregistered in {@link #hideWindow()}, but it's possible
// for {@link #doFinishInput()} to be called without {@link #hideWindow()} so we also
// unregister here.
- unregisterCompatOnBackInvokedCallback();
+ unregisterDefaultOnBackInvokedCallback();
}
void doStartInput(InputConnection ic, EditorInfo editorInfo, boolean restarting) {
@@ -4473,7 +4483,7 @@
private void compatHandleBack() {
if (!mDecorViewVisible) {
Log.e(TAG, "Back callback invoked on a hidden IME. Removing the callback...");
- unregisterCompatOnBackInvokedCallback();
+ unregisterDefaultOnBackInvokedCallback();
return;
}
final KeyEvent downEvent = createBackKeyEvent(
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 2710df2..3e4454f 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -116,6 +116,15 @@
}
flag {
+ name: "sensitive_content_improvements"
+ namespace: "permissions"
+ description: "Improvements to sensitive content/notification features, such as the Toast UX."
+ bug: "301960090"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+}
+
+flag {
name: "device_aware_permissions_enabled"
is_fixed_read_only: true
namespace: "permissions"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3248522..d91b051 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -696,6 +696,8 @@
* Output: When a package data uri is passed as input, the activity result is set to
* {@link android.app.Activity#RESULT_OK} if the permission was granted to the app. Otherwise,
* the result is set to {@link android.app.Activity#RESULT_CANCELED}.
+ *
+ * @hide
*/
@FlaggedApi(Flags.FLAG_BACKUP_TASKS_SETTINGS_SCREEN)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1000,6 +1002,21 @@
"android.settings.BLUETOOTH_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Hearing Devices.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_HEARING_DEVICES_SETTINGS =
+ "android.settings.HEARING_DEVICES_SETTINGS";
+
+ /**
* Activity action: Show Settings app search UI when this action is available for device.
* <p>
* Input: Nothing.
@@ -12434,7 +12451,7 @@
/** @hide */
public static final int PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY = 1;
/** @hide */
- public static final int PRIVATE_SPACE_AUTO_LOCK_NEVER = 2;
+ public static final int PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART = 2;
/**
* The different auto lock options for private space.
@@ -12444,7 +12461,7 @@
@IntDef(prefix = {"PRIVATE_SPACE_AUTO_LOCK_"}, value = {
PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK,
PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY,
- PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PrivateSpaceAutoLockOption {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6dcbc8e..4e521d6 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1050,12 +1050,10 @@
overlay.endDream();
mOverlayConnection.unbind();
mOverlayConnection = null;
- finish();
} catch (RemoteException e) {
Log.e(mTag, "could not inform overlay of dream end:" + e);
}
});
- return;
}
if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
diff --git a/core/java/android/service/notification/SystemZenRules.java b/core/java/android/service/notification/SystemZenRules.java
new file mode 100644
index 0000000..302efb3
--- /dev/null
+++ b/core/java/android/service/notification/SystemZenRules.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AutomaticZenRule;
+import android.app.Flags;
+import android.content.Context;
+import android.service.notification.ZenModeConfig.EventInfo;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Helper methods for schedule-type (system-owned) rules.
+ * @hide
+ */
+public final class SystemZenRules {
+
+ private static final String TAG = "SystemZenRules";
+
+ public static final String PACKAGE_ANDROID = "android";
+
+ /** Updates existing system-owned rules to use the new Modes fields (type, etc). */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static void maybeUpgradeRules(Context context, ZenModeConfig config) {
+ for (ZenRule rule : config.automaticRules.values()) {
+ if (isSystemOwnedRule(rule) && rule.type == AutomaticZenRule.TYPE_UNKNOWN) {
+ upgradeSystemProviderRule(context, rule);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the rule corresponds to a system ConditionProviderService (i.e. it is owned
+ * by the "android" package).
+ */
+ public static boolean isSystemOwnedRule(ZenRule rule) {
+ return PACKAGE_ANDROID.equals(rule.pkg);
+ }
+
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ private static void upgradeSystemProviderRule(Context context, ZenRule rule) {
+ ScheduleInfo scheduleInfo = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
+ if (scheduleInfo != null) {
+ rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ rule.triggerDescription = getTriggerDescriptionForScheduleTime(context, scheduleInfo);
+ return;
+ }
+ EventInfo eventInfo = ZenModeConfig.tryParseEventConditionId(rule.conditionId);
+ if (eventInfo != null) {
+ rule.type = AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+ rule.triggerDescription = getTriggerDescriptionForScheduleEvent(context, eventInfo);
+ return;
+ }
+ Log.wtf(TAG, "Couldn't determine type of system-owned ZenRule " + rule);
+ }
+
+ /**
+ * Updates the {@link ZenRule#triggerDescription} of the system-owned rule based on the schedule
+ * or event condition encoded in its {@link ZenRule#conditionId}.
+ *
+ * @return {@code true} if the trigger description was updated.
+ */
+ public static boolean updateTriggerDescription(Context context, ZenRule rule) {
+ ScheduleInfo scheduleInfo = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
+ if (scheduleInfo != null) {
+ return updateTriggerDescription(rule,
+ getTriggerDescriptionForScheduleTime(context, scheduleInfo));
+ }
+ EventInfo eventInfo = ZenModeConfig.tryParseEventConditionId(rule.conditionId);
+ if (eventInfo != null) {
+ return updateTriggerDescription(rule,
+ getTriggerDescriptionForScheduleEvent(context, eventInfo));
+ }
+ Log.wtf(TAG, "Couldn't determine type of system-owned ZenRule " + rule);
+ return false;
+ }
+
+ private static boolean updateTriggerDescription(ZenRule rule, String triggerDescription) {
+ if (!Objects.equals(rule.triggerDescription, triggerDescription)) {
+ rule.triggerDescription = triggerDescription;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable trigger description for a time-schedule rule (e.g. "Mon-Fri, 8:00-10:00"),
+ * using the Context's current locale.
+ */
+ @Nullable
+ public static String getTriggerDescriptionForScheduleTime(Context context,
+ @NonNull ScheduleInfo schedule) {
+ final StringBuilder sb = new StringBuilder();
+ String daysSummary = getShortDaysSummary(context, schedule);
+ if (daysSummary == null) {
+ // no use outputting times without dates
+ return null;
+ }
+ sb.append(daysSummary);
+ sb.append(context.getString(R.string.zen_mode_trigger_summary_divider_text));
+ sb.append(context.getString(
+ R.string.zen_mode_trigger_summary_range_symbol_combination,
+ timeString(context, schedule.startHour, schedule.startMinute),
+ timeString(context, schedule.endHour, schedule.endMinute)));
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns an ordered summarized list of the days on which this schedule applies, with
+ * adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed").
+ */
+ @Nullable
+ private static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
+ // Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or
+ // "Sun-Mon,Wed,Fri"
+ final int[] days = schedule.days;
+ if (days != null && days.length > 0) {
+ final StringBuilder sb = new StringBuilder();
+ final Calendar cStart = Calendar.getInstance(getLocale(context));
+ final Calendar cEnd = Calendar.getInstance(getLocale(context));
+ int[] daysOfWeek = getDaysOfWeekForLocale(cStart);
+ // the i for loop goes through days in order as determined by locale. as we walk through
+ // the days of the week, keep track of "start" and "last seen" as indicators for
+ // what's contiguous, and initialize them to something not near actual indices
+ int startDay = Integer.MIN_VALUE;
+ int lastSeenDay = Integer.MIN_VALUE;
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ final int day = daysOfWeek[i];
+
+ // by default, output if this day is *not* included in the schedule, and thus
+ // ends a previously existing block. if this day is included in the schedule
+ // after all (as will be determined in the inner for loop), then output will be set
+ // to false.
+ boolean output = (i == lastSeenDay + 1);
+ for (int j = 0; j < days.length; j++) {
+ if (day == days[j]) {
+ // match for this day in the schedule (indicated by counter i)
+ if (i == lastSeenDay + 1) {
+ // contiguous to the block we're walking through right now, record it
+ // (specifically, i, the day index) and move on to the next day
+ lastSeenDay = i;
+ output = false;
+ } else {
+ // it's a match, but not 1 past the last match, we are starting a new
+ // block
+ startDay = i;
+ lastSeenDay = i;
+ }
+
+ // if there is a match on the last day, also make sure to output at the end
+ // of this loop, and mark the day as the last day we'll have seen in the
+ // scheduled days.
+ if (i == daysOfWeek.length - 1) {
+ output = true;
+ }
+ break;
+ }
+ }
+
+ // output in either of 2 cases: this day is not a match, so has ended any previous
+ // block, or this day *is* a match but is the last day of the week, so we need to
+ // summarize
+ if (output) {
+ // either describe just the single day if startDay == lastSeenDay, or
+ // output "startDay - lastSeenDay" as a group
+ if (sb.length() > 0) {
+ sb.append(
+ context.getString(R.string.zen_mode_trigger_summary_divider_text));
+ }
+
+ SimpleDateFormat dayFormat = new SimpleDateFormat("EEE", getLocale(context));
+ if (startDay == lastSeenDay) {
+ // last group was only one day
+ cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]);
+ sb.append(dayFormat.format(cStart.getTime()));
+ } else {
+ // last group was a contiguous group of days, so group them together
+ cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]);
+ cEnd.set(Calendar.DAY_OF_WEEK, daysOfWeek[lastSeenDay]);
+ sb.append(context.getString(
+ R.string.zen_mode_trigger_summary_range_symbol_combination,
+ dayFormat.format(cStart.getTime()),
+ dayFormat.format(cEnd.getTime())));
+ }
+ }
+ }
+
+ if (sb.length() > 0) {
+ return sb.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for representing the specified time in string format.
+ */
+ private static String timeString(Context context, int hour, int minute) {
+ final Calendar c = Calendar.getInstance(getLocale(context));
+ c.set(Calendar.HOUR_OF_DAY, hour);
+ c.set(Calendar.MINUTE, minute);
+ return DateFormat.getTimeFormat(context).format(c.getTime());
+ }
+
+ private static int[] getDaysOfWeekForLocale(Calendar c) {
+ int[] daysOfWeek = new int[7];
+ int currentDay = c.getFirstDayOfWeek();
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ if (currentDay > 7) currentDay = 1;
+ daysOfWeek[i] = currentDay;
+ currentDay++;
+ }
+ return daysOfWeek;
+ }
+
+ private static Locale getLocale(Context context) {
+ return context.getResources().getConfiguration().getLocales().get(0);
+ }
+
+ /**
+ * Returns a suitable trigger description for a calendar-schedule rule (either the name of the
+ * calendar, or a message indicating all calendars are included).
+ */
+ public static String getTriggerDescriptionForScheduleEvent(Context context,
+ @NonNull EventInfo event) {
+ if (event.calName != null) {
+ return event.calName;
+ } else {
+ return context.getResources().getString(
+ R.string.zen_mode_trigger_event_calendar_any);
+ }
+ }
+
+ private SystemZenRules() {}
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 1d6dd1e..610a317 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1773,7 +1773,12 @@
return true;
}
+ /**
+ * Returns the {@link ScheduleInfo} encoded in the condition id, or {@code null} if it could not
+ * be decoded.
+ */
@UnsupportedAppUsage
+ @Nullable
public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
final boolean isSchedule = conditionId != null
&& Condition.SCHEME.equals(conditionId.getScheme())
@@ -1882,6 +1887,11 @@
return tryParseEventConditionId(conditionId) != null;
}
+ /**
+ * Returns the {@link EventInfo} encoded in the condition id, or {@code null} if it could not be
+ * decoded.
+ */
+ @Nullable
public static EventInfo tryParseEventConditionId(Uri conditionId) {
final boolean isEvent = conditionId != null
&& Condition.SCHEME.equals(conditionId.getScheme())
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index 908ab5f..a708718 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -47,4 +47,5 @@
void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
void notifyInferenceServiceConnected();
void notifyInferenceServiceDisconnected();
+ void ready();
}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 86320b8..5dc540a 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Service;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
@@ -111,6 +112,11 @@
return new IOnDeviceIntelligenceService.Stub() {
/** {@inheritDoc} */
@Override
+ public void ready() {
+ OnDeviceIntelligenceService.this.onReady();
+ }
+
+ @Override
public void getVersion(RemoteCallback remoteCallback) {
Objects.requireNonNull(remoteCallback);
OnDeviceIntelligenceService.this.onGetVersion(l -> {
@@ -208,6 +214,16 @@
return null;
}
+ /**
+ * Using this signal to assertively a signal each time service binds successfully, used only in
+ * tests to get a signal that service instance is ready. This is needed because we cannot rely
+ * on {@link #onCreate} or {@link #onBind} to be invoke on each binding.
+ *
+ * @hide
+ */
+ @TestApi
+ public void onReady() {}
+
/**
* Invoked when a new instance of the remote inference service is created.
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index e6d8fd0..8a3f6ce 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -71,9 +71,11 @@
@Nullable
static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
- String defaultAppPackageName = getDefaultWalletApp(context);
+ String defaultAppPackageName = null;
- if (defaultAppPackageName == null) {
+ if (isWalletRoleAvailable(context)) {
+ defaultAppPackageName = getDefaultWalletApp(context);
+ } else {
ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
if (defaultPaymentApp == null) {
return null;
@@ -103,14 +105,21 @@
final long token = Binder.clearCallingIdentity();
try {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
- List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
- return roleHolders.isEmpty() ? null : roleHolders.get(0);
- }
+ List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+ return roleHolders.isEmpty() ? null : roleHolders.get(0);
} finally {
Binder.restoreCallingIdentity(token);
}
- return null;
+ }
+
+ private static boolean isWalletRoleAvailable(Context context) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ return roleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private static ComponentName getDefaultPaymentApp(Context context) {
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
index ca2e56d..2e39f73 100644
--- a/core/java/android/view/BatchedInputEventReceiver.java
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -29,6 +29,7 @@
private Choreographer mChoreographer;
private boolean mBatchingEnabled;
private boolean mBatchedInputScheduled;
+ private final String mTag;
private final Handler mHandler;
private final Runnable mConsumeBatchedInputEvents = new Runnable() {
@Override
@@ -43,6 +44,7 @@
super(inputChannel, looper);
mChoreographer = choreographer;
mBatchingEnabled = true;
+ mTag = inputChannel.getName();
traceBoolVariable("mBatchingEnabled", mBatchingEnabled);
traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled);
mHandler = new Handler(looper);
@@ -123,7 +125,12 @@
private final class BatchedInputRunnable implements Runnable {
@Override
public void run() {
- doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_INPUT, mTag);
+ doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_INPUT);
+ }
}
}
private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
new file mode 100644
index 0000000..d14e858
--- /dev/null
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Log;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
+
+/**
+ * Controller for IME predictive back animation
+ *
+ * @hide
+ */
+public class ImeBackAnimationController implements OnBackAnimationCallback {
+
+ private static final String TAG = "ImeBackAnimationController";
+ private static final int POST_COMMIT_DURATION_MS = 200;
+ private static final int POST_COMMIT_CANCEL_DURATION_MS = 50;
+ private static final float PEEK_FRACTION = 0.1f;
+ private static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+ private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+ private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
+
+ private final InsetsController mInsetsController;
+ private final ViewRootImpl mViewRoot;
+ private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
+ private ValueAnimator mPostCommitAnimator = null;
+ private float mLastProgress = 0f;
+ private boolean mTriggerBack = false;
+ private boolean mIsPreCommitAnimationInProgress = false;
+
+ public ImeBackAnimationController(ViewRootImpl viewRoot) {
+ mInsetsController = viewRoot.getInsetsController();
+ mViewRoot = viewRoot;
+ }
+
+ @Override
+ public void onBackStarted(@NonNull BackEvent backEvent) {
+ if (isAdjustResize()) {
+ // There is no good solution for a predictive back animation if the app uses
+ // adjustResize, since we can't relayout the whole app for every frame. We also don't
+ // want to reveal any black areas behind the IME. Therefore let's not play any animation
+ // in that case for now.
+ Log.d(TAG, "onBackStarted -> not playing predictive back animation due to softinput"
+ + " mode adjustResize");
+ return;
+ }
+ if (isHideAnimationInProgress()) {
+ // If IME is currently animating away, skip back gesture
+ return;
+ }
+ mIsPreCommitAnimationInProgress = true;
+ if (mWindowInsetsAnimationController != null) {
+ // There's still an active animation controller. This means that a cancel post commit
+ // animation of an earlier back gesture is still in progress. Let's cancel it and let
+ // the new gesture seamlessly take over.
+ resetPostCommitAnimator();
+ setPreCommitProgress(0f);
+ return;
+ }
+ mInsetsController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+ new WindowInsetsAnimationControlListener() {
+ @Override
+ public void onReady(@NonNull WindowInsetsAnimationController controller,
+ @WindowInsets.Type.InsetsType int types) {
+ mWindowInsetsAnimationController = controller;
+ if (mIsPreCommitAnimationInProgress) {
+ setPreCommitProgress(mLastProgress);
+ } else {
+ // gesture has already finished before IME became ready to animate
+ startPostCommitAnim(mTriggerBack);
+ }
+ }
+
+ @Override
+ public void onFinished(@NonNull WindowInsetsAnimationController controller) {
+ reset();
+ }
+
+ @Override
+ public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+ reset();
+ }
+ }, /*fromIme*/ false, /*durationMs*/ -1, /*interpolator*/ null, ANIMATION_TYPE_USER,
+ /*fromPredictiveBack*/ true);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mLastProgress = backEvent.getProgress();
+ setPreCommitProgress(mLastProgress);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ if (isAdjustResize()) return;
+ startPostCommitAnim(/*hideIme*/ false);
+ }
+
+ @Override
+ public void onBackInvoked() {
+ if (isAdjustResize()) {
+ mInsetsController.hide(ime());
+ return;
+ }
+ startPostCommitAnim(/*hideIme*/ true);
+ }
+
+ private void setPreCommitProgress(float progress) {
+ if (isHideAnimationInProgress()) return;
+ if (mWindowInsetsAnimationController != null) {
+ float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
+ float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
+ float imeHeight = shownY - hiddenY;
+ float interpolatedProgress = STANDARD_DECELERATE.getInterpolation(progress);
+ int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
+ mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
+ progress);
+ }
+ }
+
+ private void startPostCommitAnim(boolean triggerBack) {
+ mIsPreCommitAnimationInProgress = false;
+ if (mWindowInsetsAnimationController == null || isHideAnimationInProgress()) {
+ mTriggerBack = triggerBack;
+ return;
+ }
+ mTriggerBack = triggerBack;
+ int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom;
+ int targetBottomInset;
+ if (triggerBack) {
+ targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
+ } else {
+ targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom;
+ }
+ mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset);
+ mPostCommitAnimator.setInterpolator(
+ triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
+ mPostCommitAnimator.addUpdateListener(animation -> {
+ int bottomInset = (int) ((float) animation.getAnimatedValue());
+ if (mWindowInsetsAnimationController != null) {
+ mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset),
+ 1f, animation.getAnimatedFraction());
+ } else {
+ reset();
+ }
+ });
+ mPostCommitAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mIsPreCommitAnimationInProgress) {
+ // this means a new gesture has started while the cancel-post-commit-animation
+ // was in progress. Let's not reset anything and let the new user gesture take
+ // over seamlessly
+ return;
+ }
+ if (mWindowInsetsAnimationController != null) {
+ mWindowInsetsAnimationController.finish(!triggerBack);
+ }
+ reset();
+ }
+ });
+ mPostCommitAnimator.setDuration(
+ triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
+ mPostCommitAnimator.start();
+ }
+
+ private void reset() {
+ mWindowInsetsAnimationController = null;
+ resetPostCommitAnimator();
+ mLastProgress = 0f;
+ mTriggerBack = false;
+ mIsPreCommitAnimationInProgress = false;
+ }
+
+ private void resetPostCommitAnimator() {
+ if (mPostCommitAnimator != null) {
+ mPostCommitAnimator.cancel();
+ mPostCommitAnimator = null;
+ }
+ }
+
+ private boolean isAdjustResize() {
+ return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
+ == SOFT_INPUT_ADJUST_RESIZE;
+ }
+
+ private boolean isHideAnimationInProgress() {
+ return mPostCommitAnimator != null && mTriggerBack;
+ }
+
+}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2fcffd0..90aafbd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,6 +29,8 @@
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.ime;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -322,7 +324,7 @@
public static final int ANIMATION_TYPE_HIDE = 1;
/** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public static final int ANIMATION_TYPE_USER = 2;
/** Running animation will resize insets */
@@ -1076,7 +1078,7 @@
show(types, false /* fromIme */, null /* statsToken */);
}
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @VisibleForTesting(visibility = PACKAGE)
public void show(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
if ((types & ime()) != 0) {
@@ -1260,14 +1262,15 @@
@Nullable CancellationSignal cancellationSignal,
@NonNull WindowInsetsAnimationControlListener listener) {
controlWindowInsetsAnimation(types, cancellationSignal, listener,
- false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER);
+ false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER,
+ false /* fromPredictiveBack */);
}
- private void controlWindowInsetsAnimation(@InsetsType int types,
+ void controlWindowInsetsAnimation(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener,
boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
- @AnimationType int animationType) {
+ @AnimationType int animationType, boolean fromPredictiveBack) {
if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) {
listener.onCancelled(null);
return;
@@ -1279,7 +1282,8 @@
}
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
- interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
+ interpolator, animationType,
+ getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
false /* useInsetsAnimationThread */, null /* statsToken */);
}
@@ -1526,7 +1530,12 @@
}
private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
- @InsetsType int types) {
+ @InsetsType int types, boolean fromPredictiveBack) {
+ if (fromPredictiveBack) {
+ // When insets are animated by predictive back, we want insets to be shown to prevent a
+ // jump cut from shown to hidden at the start of the predictive back animation
+ return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+ }
// Generally, we want to layout the opposite of the current state. This is to make animation
// callbacks easy to use: The can capture the layout values and then treat that as end-state
// during the animation.
@@ -1730,7 +1739,7 @@
return ANIMATION_TYPE_NONE;
}
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @VisibleForTesting(visibility = PACKAGE)
public void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
final @InsetsType int requestedVisibleTypes =
(mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 147d562..17d1404 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -16,8 +16,10 @@
package android.view;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.annotation.XmlRes;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -39,6 +41,7 @@
import android.os.PointerIconType;
import android.util.Log;
import android.util.SparseArray;
+import android.view.flags.Flags;
import com.android.internal.util.XmlUtils;
@@ -637,4 +640,15 @@
default: return Integer.toString(type);
}
}
+
+ /**
+ * Sets whether drop shadow will draw in the native code.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_ENABLE_VECTOR_CURSORS)
+ public void setDrawNativeDropShadow(boolean drawNativeDropShadow) {
+ mDrawNativeDropShadow = drawNativeDropShadow;
+ }
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index c66abe8..5466bf5 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
* @hide
*/
@Override
- protected int calculateFrameRateCategory(int width, int height) {
+ protected int calculateFrameRateCategory() {
if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
return FRAME_RATE_CATEGORY_NORMAL;
}
- return super.calculateFrameRateCategory(width, height);
+ return super.calculateFrameRateCategory();
}
@UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 736e815..b6df1bb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5695,17 +5695,10 @@
private ViewTranslationResponse mViewTranslationResponse;
/**
- * Threshold size for something to be considered a small area update (in DP).
- * This is the dimension for both width and height.
+ * The multiplier for mAttachInfo.mSmallSizePixels to consider a View to be small
+ * if both dimensions are smaller than this.
*/
- private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
-
- /**
- * Threshold size for something to be considered a small area update (in DP) if
- * it is narrow. This is for either width OR height. For example, a narrow progress
- * bar could be considered a small area.
- */
- private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
+ private static final int FRAME_RATE_SQUARE_SMALL_SIZE_MULTIPLIER = 4;
private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -5740,6 +5733,8 @@
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4;
+ private int mSizeBasedFrameRateCategoryAndReason;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -23561,6 +23556,9 @@
return renderNode;
}
+ mLastFrameX = mLeft + mRenderNode.getTranslationX();
+ mLastFrameY = mTop + mRenderNode.getTranslationY();
+
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
@@ -24724,8 +24722,6 @@
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
mFrameContentVelocity = -1;
- mLastFrameX = mLeft + mRenderNode.getTranslationX();
- mLastFrameY = mTop + mRenderNode.getTranslationY();
/*
* Draw traversal performs several drawing steps which must be executed
@@ -25474,6 +25470,21 @@
}
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ if (mAttachInfo != null) {
+ int narrowSize = mAttachInfo.mSmallSizePixels;
+ int smallSize = narrowSize * FRAME_RATE_SQUARE_SMALL_SIZE_MULTIPLIER;
+ if (newWidth <= narrowSize || newHeight <= narrowSize
+ || (newWidth <= smallSize && newHeight <= smallSize)) {
+ int category = toolkitFrameRateBySizeReadOnly()
+ ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
+ mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_SMALL;
+ } else {
+ int category = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_LARGE;
+ }
+ }
+
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
@@ -32040,6 +32051,13 @@
int mSensitiveViewsCount;
/**
+ * The size used for a View to be considered small for the purposes of using
+ * low refresh rate by default. This is the size in one direction, so a long, thin
+ * item like a progress bar can be compared to this.
+ */
+ final int mSmallSizePixels;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
@@ -32056,6 +32074,7 @@
mHandler = handler;
mRootCallbacks = effectPlayer;
mTreeObserver = new ViewTreeObserver(context);
+ mSmallSizePixels = (int) (context.getResources().getDisplayMetrics().density * 10);
}
void increaseSensitiveViewsCount() {
@@ -33784,28 +33803,10 @@
*
* @hide
*/
- protected int calculateFrameRateCategory(int width, int height) {
+ protected int calculateFrameRateCategory() {
if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
< INFREQUENT_UPDATE_INTERVAL_MILLIS) {
- DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
- float density = displayMetrics.density;
- if (density == 0f) {
- density = 1f;
- }
- float widthDp = width / density;
- float heightDp = height / density;
- if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
- || heightDp <= FRAME_RATE_NARROW_THRESHOLD
- || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
- && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
- int category = toolkitFrameRateBySizeReadOnly()
- ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
- return category | FRAME_RATE_CATEGORY_REASON_SMALL;
- } else {
- int category = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
- return category | FRAME_RATE_CATEGORY_REASON_LARGE;
- }
+ return mSizeBasedFrameRateCategoryAndReason;
}
if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
@@ -33823,7 +33824,14 @@
if (viewVelocityApi()) {
float velocity = mFrameContentVelocity;
if (velocity < 0f) {
- velocity = calculateVelocity();
+ // This current calculation is very simple. If something on the screen moved,
+ // then it votes for the highest velocity. If it doesn't move, then return 0.
+ RenderNode renderNode = mRenderNode;
+ float x = mLeft + renderNode.getTranslationX();
+ float y = mTop + renderNode.getTranslationY();
+
+ velocity = (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+ ? 100_000f : 0f;
}
if (velocity > 0f) {
float frameRate = convertVelocityToFrameRate(velocity);
@@ -33831,43 +33839,55 @@
return;
}
}
- if (sToolkitMetricsForFrameRateDecisionFlagValue) {
- float sizePercentage = getSizePercentage();
- viewRootImpl.recordViewPercentage(sizePercentage);
- }
- int frameRateCategory;
- if (Float.isNaN(mPreferredFrameRate)) {
- frameRateCategory = calculateFrameRateCategory(width, height);
- } else if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
- | FRAME_RATE_CATEGORY_REASON_REQUESTED;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCategory = FRAME_RATE_CATEGORY_LOW
- | FRAME_RATE_CATEGORY_REASON_REQUESTED;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
- | FRAME_RATE_CATEGORY_REASON_REQUESTED;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCategory = FRAME_RATE_CATEGORY_HIGH
- | FRAME_RATE_CATEGORY_REASON_REQUESTED;
- } else {
- // invalid frame rate, use default
- int category = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
- frameRateCategory = category
- | FRAME_RATE_CATEGORY_REASON_INVALID;
+ if (!willNotDraw()) {
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ float sizePercentage = getSizePercentage();
+ viewRootImpl.recordViewPercentage(sizePercentage);
}
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
- mFrameRateCompatibility);
- return;
- }
- int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
- int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
- viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
- mLastFrameRateCategory = frameRateCategory;
+ int frameRateCategory;
+ if (Float.isNaN(mPreferredFrameRate)) {
+ if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
+ < INFREQUENT_UPDATE_INTERVAL_MILLIS && mAttachInfo != null) {
+ frameRateCategory = mSizeBasedFrameRateCategoryAndReason;
+ } else if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
+ frameRateCategory =
+ FRAME_RATE_CATEGORY_NORMAL
+ | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+ } else {
+ frameRateCategory = mLastFrameRateCategory;
+ }
+ } else if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCategory = FRAME_RATE_CATEGORY_LOW
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else {
+ // invalid frame rate, use default
+ int category = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ frameRateCategory = category
+ | FRAME_RATE_CATEGORY_REASON_INVALID;
+ }
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+ mFrameRateCompatibility);
+ return;
+ }
+
+ int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
+ mLastFrameRateCategory = frameRateCategory;
+ }
}
}
@@ -33878,16 +33898,6 @@
return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
}
- private float calculateVelocity() {
- // This current calculation is very simple. If something on the screen moved, then
- // it votes for the highest velocity. If it doesn't move, then return 0.
- float x = mLeft + mRenderNode.getTranslationX();
- float y = mTop + mRenderNode.getTranslationY();
-
- return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
- ? 100_000f : 0f;
- }
-
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 85d3688..0d9e471 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -110,10 +110,12 @@
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -941,6 +943,7 @@
new InputEventConsistencyVerifier(this, 0) : null;
private final InsetsController mInsetsController;
+ private final ImeBackAnimationController mImeBackAnimationController;
private final ImeFocusController mImeFocusController;
private boolean mIsSurfaceOpaque;
@@ -1066,11 +1069,6 @@
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
- // Used to allow developers to opt out Toolkit dVRR feature.
- // This feature allows device to adjust refresh rate
- // as needed and can be useful for power saving.
- // Should not enable the dVRR feature if the value is false.
- private boolean mIsFrameRatePowerSavingsBalanced = true;
// Used to check if there is a conflict between different frame rate voting.
// Take 24 and 30 as an example, 24 is not a divisor of 30.
// We consider there is a conflict.
@@ -1153,6 +1151,7 @@
private String mLargestViewTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
@@ -1160,6 +1159,8 @@
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
+ sToolkitFrameRateFunctionEnablingReadOnlyFlagValue =
+ toolkitFrameRateFunctionEnablingReadOnly();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1207,6 +1208,7 @@
// TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
mChoreographer = Choreographer.getInstance();
mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
+ mImeBackAnimationController = new ImeBackAnimationController(this);
mHandwritingInitiator = new HandwritingInitiator(
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
@@ -3182,7 +3184,7 @@
== LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public InsetsController getInsetsController() {
return mInsetsController;
}
@@ -10171,13 +10173,18 @@
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
- mConsumeBatchedInputScheduled = false;
- if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {
- // If we consumed a batch here, we want to go ahead and schedule the
- // consumption of batched input events on the next frame. Otherwise, we would
- // wait until we have more input events pending and might get starved by other
- // things occurring in the process.
- scheduleConsumeBatchedInput();
+ Trace.traceBegin(TRACE_TAG_VIEW, mTag);
+ try {
+ mConsumeBatchedInputScheduled = false;
+ if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) {
+ // If we consumed a batch here, we want to go ahead and schedule the
+ // consumption of batched input events on the next frame. Otherwise, we would
+ // wait until we have more input events pending and might get starved by other
+ // things occurring in the process.
+ scheduleConsumeBatchedInput();
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIEW);
}
}
}
@@ -12149,7 +12156,8 @@
+ "IWindow:%s Session:%s",
mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
}
- mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
+ mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow,
+ mImeBackAnimationController);
}
private void sendBackKeyEvent(int action) {
@@ -12631,12 +12639,11 @@
}
mHasInvalidation = true;
checkIdleness();
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)
- && mPreferredFrameRateCategory != oldCategory
+ if (mPreferredFrameRateCategory != oldCategory
&& mPreferredFrameRateCategory == frameRateCategory
) {
mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view.getClass().getSimpleName();
+ mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
}
}
@@ -12777,7 +12784,10 @@
*/
@VisibleForTesting
public boolean isFrameRatePowerSavingsBalanced() {
- return mIsFrameRatePowerSavingsBalanced;
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return mWindowAttributes.isFrameRatePowerSavingsBalanced();
+ }
+ return true;
}
/**
@@ -12789,21 +12799,12 @@
return mIsFrameRateConflicted;
}
- /**
- * Set the value of mIsFrameRatePowerSavingsBalanced
- * Can be used to checked if toolkit dVRR feature is enabled.
- */
- public void setFrameRatePowerSavingsBalanced(boolean enabled) {
- if (sToolkitSetFrameRateReadOnlyFlagValue) {
- mIsFrameRatePowerSavingsBalanced = enabled;
- }
- }
-
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- // return sToolkitSetFrameRateReadOnlyFlagValue && mIsFrameRatePowerSavingsBalanced;
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+ }
return false;
-
}
private void checkIdleness() {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 51229a7..1ebced5 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -334,7 +334,12 @@
private boolean mOverlayWithDecorCaptionEnabled = true;
private boolean mCloseOnSwipeEnabled = false;
- private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ /**
+ * To check if toolkitSetFrameRateReadOnly flag is enabled
+ *
+ * @hide
+ */
+ protected static boolean sToolkitSetFrameRateReadOnlyFlagValue =
android.view.flags.Flags.toolkitSetFrameRateReadOnly();
// The current window attributes.
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 06598b3..1d4d18b 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -86,4 +86,12 @@
description: "Feature flag for suppressing boost on typing"
bug: "239979904"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_function_enabling_read_only"
+ namespace: "toolkit"
+ description: "Feature flag to enable the functionality of the dVRR feature"
+ bug: "239979904"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index cedf8d0..f454a6a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -770,6 +770,20 @@
}
@AnyThread
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+ static void finishTrackingPendingImeVisibilityRequests() {
+ final var service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.finishTrackingPendingImeVisibilityRequests();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
@Nullable
private static IImeTracker getImeTrackerService() {
var trackerService = sTrackerServiceCache;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8efb201..80b2396 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -4316,6 +4316,19 @@
}
/**
+ * A test API for CTS to finish the tracking of any pending IME visibility requests. This
+ * won't stop the actual requests, but allows resetting the state when starting up test runs.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+ public void finishTrackingPendingImeVisibilityRequests() {
+ IInputMethodManagerGlobalInvoker.finishTrackingPendingImeVisibilityRequests();
+ }
+
+ /**
* Show the settings for enabling subtypes of the specified input method.
*
* @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index e14ae72..c7900e4 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -104,7 +104,8 @@
sPackage = packageInfo;
// If multi-process is not enabled, then do not start the zygote service.
- if (!sMultiprocessEnabled) {
+ // Only check sMultiprocessEnabled if updateServiceV2 is not enabled.
+ if (!updateServiceV2() && !sMultiprocessEnabled) {
return;
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f54ef38..ab6b512 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
@@ -5098,6 +5100,11 @@
boolean more = scroller.computeScrollOffset();
final int y = scroller.getCurrY();
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
// Flip sign to convert finger direction to list items direction
// (e.g. finger moving down means list is moving towards the top)
int delta = consumeFlingInStretch(mLastFlingY - y);
@@ -5192,6 +5199,10 @@
invalidate();
postOnAnimation(this);
}
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
} else {
endFling();
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 0cc9a0d..da1993d 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -148,8 +148,17 @@
@OnBackInvokedDispatcher.Priority int priority,
int callbackId,
@NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
- final ImeOnBackInvokedCallback imeCallback =
- new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
+ final ImeOnBackInvokedCallback imeCallback;
+ if (priority == PRIORITY_SYSTEM) {
+ // A callback registration with PRIORITY_SYSTEM indicates that a predictive back
+ // animation can be played on the IME. Therefore register the
+ // DefaultImeOnBackInvokedCallback with the receiving dispatcher and override the
+ // priority to PRIORITY_DEFAULT.
+ priority = PRIORITY_DEFAULT;
+ imeCallback = new DefaultImeOnBackAnimationCallback(iCallback, callbackId, priority);
+ } else {
+ imeCallback = new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
+ }
mImeCallbacks.add(imeCallback);
receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
}
@@ -230,6 +239,17 @@
}
/**
+ * Subclass of ImeOnBackInvokedCallback indicating that a predictive IME back animation may be
+ * played instead of invoking the callback.
+ */
+ static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
+ DefaultImeOnBackAnimationCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
+ int priority) {
+ super(iCallback, id, priority);
+ }
+ }
+
+ /**
* Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to
* another {@link ViewRootImpl} on focus change.
*
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 45d7767..bcbac93 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -31,6 +31,7 @@
import android.util.Log;
import android.view.IWindow;
import android.view.IWindowSession;
+import android.view.ImeBackAnimationController;
import androidx.annotation.VisibleForTesting;
@@ -71,6 +72,9 @@
@Nullable
private ImeOnBackInvokedDispatcher mImeDispatcher;
+ @Nullable
+ private ImeBackAnimationController mImeBackAnimationController;
+
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
@@ -88,9 +92,11 @@
* Sends the pending top callback (if one exists) to WM when the view root
* is attached a window.
*/
- public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
+ public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
+ @Nullable ImeBackAnimationController imeBackAnimationController) {
mWindowSession = windowSession;
mWindow = window;
+ mImeBackAnimationController = imeBackAnimationController;
if (!mAllCallbacks.isEmpty()) {
setTopOnBackInvokedCallback(getTopCallback());
}
@@ -101,6 +107,7 @@
clear();
mWindow = null;
mWindowSession = null;
+ mImeBackAnimationController = null;
}
// TODO: Take an Executor for the callback to run on.
@@ -125,6 +132,9 @@
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
// If callback has already been added, remove it and re-add it.
@@ -152,6 +162,9 @@
mImeDispatcher.unregisterOnBackInvokedCallback(callback);
return;
}
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
if (!mAllCallbacks.containsKey(callback)) {
if (DEBUG) {
Log.i(TAG, "Callback not found. returning...");
@@ -199,7 +212,7 @@
}
} else {
Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
- + "callback=" + callback);
+ + " callback=" + callback);
}
}
@@ -243,9 +256,9 @@
int priority = mAllCallbacks.get(callback);
final IOnBackInvokedCallback iCallback =
callback instanceof ImeOnBackInvokedDispatcher
- .ImeOnBackInvokedCallback
+ .ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
- callback).getIOnBackInvokedCallback()
+ callback).getIOnBackInvokedCallback()
: new OnBackInvokedCallbackWrapper(
callback,
mProgressAnimator,
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index 590e88b..2d1cbb5 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -8,7 +8,7 @@
}
flag {
- name: "magnification_always_draw_fullscreen_border"
+ name: "always_draw_magnification_fullscreen_border"
namespace: "accessibility"
description: "Always draw fullscreen orange border in fullscreen magnification"
bug: "291891390"
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 7fbec67..fa0dab0 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -86,3 +86,10 @@
description: "Whether the blurred letterbox wallpaper background is enabled by default"
bug: "297195682"
}
+
+flag {
+ name: "enable_compatui_sysui_launcher"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Enables sysui animation for user aspect ratio button"
+ bug: "300357441"
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index c70febb..34b487f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -37,7 +37,6 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.widget.AdapterView;
import com.android.internal.R;
@@ -248,12 +247,10 @@
boolean allowEditing = isUserSetupCompleted(this);
boolean showWhenLocked = false;
- if (Flags.allowShortcutChooserOnLockscreen()) {
- final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
- if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
- allowEditing = false;
- showWhenLocked = true;
- }
+ final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+ if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
+ allowEditing = false;
+ showWhenLocked = true;
}
if (allowEditing) {
final String positiveButtonText =
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 751368f..6620156 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -373,7 +373,7 @@
.putExtra(Intent.EXTRA_USER_ID, userId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- if (crossUserSuspensionEnabled()) {
+ if (crossUserSuspensionEnabled() && suspendingPackage != null) {
intent.putExtra(EXTRA_SUSPENDING_USER, suspendingPackage.userId);
}
return intent;
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index b45bc1c..ab4edb6 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -86,4 +86,13 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
boolean hasPendingImeVisibilityRequests();
+
+ /**
+ * Finishes the tracking of any pending IME visibility requests. This won't stop the actual
+ * requests, but allows resetting the state when starting up test runs.
+ */
+ @EnforcePermission("TEST_INPUT_METHOD")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.TEST_INPUT_METHOD)")
+ oneway void finishTrackingPendingImeVisibilityRequests();
}
diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java
index 1b18485..f8eb5be 100644
--- a/core/java/com/android/internal/net/ConnectivityBlobStore.java
+++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.ContentValues;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
@@ -153,8 +154,11 @@
final List<String> names = new ArrayList<String>();
try (Cursor cursor = mDb.query(TABLENAME,
new String[] {"name"} /* columns */,
- "owner=? AND name LIKE ?" /* selection */,
- new String[] {Integer.toString(ownerUid), prefix + "%"} /* selectionArgs */,
+ "owner=? AND name LIKE ? ESCAPE '\\'" /* selection */,
+ new String[] {
+ Integer.toString(ownerUid),
+ DatabaseUtils.escapeForLike(prefix) + "%"
+ } /* selectionArgs */,
null /* groupBy */,
null /* having */,
"name ASC" /* orderBy */)) {
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index c14d8d8..8063be6 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -45,8 +45,8 @@
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;
- static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
- new BinderProxyLimitListenerDelegate();
+ static final BinderProxyCountEventListenerDelegate sBinderProxyCountEventListenerDelegate =
+ new BinderProxyCountEventListenerDelegate();
static final class GcWatcher {
@Override
@@ -226,15 +226,24 @@
* @param low The threshold a binder count must drop below before the callback
* can be called again. (This is to avoid many repeated calls to the
* callback in a brief period of time)
+ * @param warning The threshold between {@code high} and {@code low} where if the binder count
+ * exceeds that, the warning callback would be triggered.
*/
- public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+ public static final native void nSetBinderProxyCountWatermarks(int high, int low, int warning);
/**
* Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
* be called with the uid of the app causing too many Binder Proxies
*/
- public interface BinderProxyLimitListener {
+ public interface BinderProxyCountEventListener {
public void onLimitReached(int uid);
+
+ /**
+ * Call when the number of binder proxies from the uid of the app reaches
+ * the warning threshold.
+ */
+ default void onWarningThresholdReached(int uid) {
+ }
}
/**
@@ -243,7 +252,17 @@
* @param uid The uid of the bad behaving app sending too many binders
*/
public static void binderProxyLimitCallbackFromNative(int uid) {
- sBinderProxyLimitListenerDelegate.notifyClient(uid);
+ sBinderProxyCountEventListenerDelegate.notifyLimitReached(uid);
+ }
+
+ /**
+ * Callback used by native code to trigger a callback in java code. The callback will be
+ * triggered when too many binder proxies from a uid hits the warning limit.
+ * @param uid The uid of the bad behaving app sending too many binders
+ */
+ @SuppressWarnings("unused")
+ public static void binderProxyWarningCallbackFromNative(int uid) {
+ sBinderProxyCountEventListenerDelegate.notifyWarningReached(uid);
}
/**
@@ -252,41 +271,45 @@
* @param handler must not be null, callback will be posted through the handler;
*
*/
- public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+ public static void setBinderProxyCountCallback(BinderProxyCountEventListener listener,
@NonNull Handler handler) {
Preconditions.checkNotNull(handler,
"Must provide NonNull Handler to setBinderProxyCountCallback when setting "
- + "BinderProxyLimitListener");
- sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+ + "BinderProxyCountEventListener");
+ sBinderProxyCountEventListenerDelegate.setListener(listener, handler);
}
/**
* Clear the Binder Proxy callback
*/
public static void clearBinderProxyCountCallback() {
- sBinderProxyLimitListenerDelegate.setListener(null, null);
+ sBinderProxyCountEventListenerDelegate.setListener(null, null);
}
- static private class BinderProxyLimitListenerDelegate {
- private BinderProxyLimitListener mBinderProxyLimitListener;
+ private static class BinderProxyCountEventListenerDelegate {
+ private BinderProxyCountEventListener mBinderProxyCountEventListener;
private Handler mHandler;
- void setListener(BinderProxyLimitListener listener, Handler handler) {
+ void setListener(BinderProxyCountEventListener listener, Handler handler) {
synchronized (this) {
- mBinderProxyLimitListener = listener;
+ mBinderProxyCountEventListener = listener;
mHandler = handler;
}
}
- void notifyClient(final int uid) {
+ void notifyLimitReached(final int uid) {
synchronized (this) {
- if (mBinderProxyLimitListener != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mBinderProxyLimitListener.onLimitReached(uid);
- }
- });
+ if (mBinderProxyCountEventListener != null) {
+ mHandler.post(() -> mBinderProxyCountEventListener.onLimitReached(uid));
+ }
+ }
+ }
+
+ void notifyWarningReached(final int uid) {
+ synchronized (this) {
+ if (mBinderProxyCountEventListener != null) {
+ mHandler.post(() ->
+ mBinderProxyCountEventListener.onWarningThresholdReached(uid));
}
}
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 9df93f9..e12becd 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -407,8 +407,10 @@
*/
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
int flags) {
+ // The signature parsing will be done later in method parseBaseApk.
+ int liteParseFlags = flags & ~PARSE_COLLECT_CERTIFICATES;
final ParseResult<PackageLite> liteResult =
- ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
+ ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, liteParseFlags);
if (liteResult.isError()) {
return input.error(liteResult);
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 9868ceb..02cb53e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -365,8 +365,6 @@
private boolean mUseDecorContext = false;
- private boolean mIsFrameRatePowerSavingsBalanced = true;
-
/** @see ViewRootImpl#mActivityConfigCallback */
private ActivityConfigCallback mActivityConfigCallback;
@@ -2216,7 +2214,6 @@
void onViewRootImplSet(ViewRootImpl viewRoot) {
viewRoot.setActivityConfigCallback(mActivityConfigCallback);
viewRoot.getOnBackInvokedDispatcher().updateContext(getContext());
- viewRoot.setFrameRatePowerSavingsBalanced(mIsFrameRatePowerSavingsBalanced);
mProxyOnBackInvokedDispatcher.setActualDispatcher(viewRoot.getOnBackInvokedDispatcher());
applyDecorFitsSystemWindows();
}
@@ -2566,8 +2563,11 @@
requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
if (a.hasValue(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced)) {
- mIsFrameRatePowerSavingsBalanced =
- a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced, true);
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ setFrameRatePowerSavingsBalanced(
+ a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced,
+ true));
+ }
}
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
index ce8ca0d..2d36536 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -21,6 +21,11 @@
* Interface for the companion operations
*/
public interface CompanionOperation {
+ /**
+ * Read, create and add instance to operations
+ * @param buffer data to read to create operation
+ * @param operations command is to be added
+ */
void read(WireBuffer buffer, List<Operation> operations);
// Debugging / Documentation utility functions
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 0e4c743..55f2dee 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -331,6 +331,7 @@
public void initFromBuffer(RemoteComposeBuffer buffer) {
mOperations = new ArrayList<Operation>();
buffer.inflateFromBuffer(mOperations);
+ mBuffer = buffer;
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index b8bb1f0..54b277a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -17,8 +17,29 @@
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawArc;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
+import com.android.internal.widget.remotecompose.core.operations.DrawLine;
+import com.android.internal.widget.remotecompose.core.operations.DrawOval;
+import com.android.internal.widget.remotecompose.core.operations.DrawPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
+import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
+import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
import com.android.internal.widget.remotecompose.core.operations.TextData;
@@ -48,7 +69,30 @@
public static final int DATA_BITMAP = 101;
public static final int DATA_TEXT = 102;
+/////////////////////////////=====================
+ public static final int CLIP_PATH = 38;
+ public static final int CLIP_RECT = 39;
+ public static final int PAINT_VALUES = 40;
+ public static final int DRAW_RECT = 42;
+ public static final int DRAW_TEXT_RUN = 43;
+ public static final int DRAW_CIRCLE = 46;
+ public static final int DRAW_LINE = 47;
+ public static final int DRAW_ROUND_RECT = 51;
+ public static final int DRAW_ARC = 52;
+ public static final int DRAW_TEXT_ON_PATH = 53;
+ public static final int DRAW_OVAL = 56;
+ public static final int DATA_PATH = 123;
+ public static final int DRAW_PATH = 124;
+ public static final int DRAW_TWEEN_PATH = 125;
+ public static final int MATRIX_SCALE = 126;
+ public static final int MATRIX_TRANSLATE = 127;
+ public static final int MATRIX_SKEW = 128;
+ public static final int MATRIX_ROTATE = 129;
+ public static final int MATRIX_SAVE = 130;
+ public static final int MATRIX_RESTORE = 131;
+ public static final int MATRIX_SET = 132;
+ /////////////////////////////////////////======================
public static IntMap<CompanionOperation> map = new IntMap<>();
static {
@@ -60,6 +104,29 @@
map.put(CLICK_AREA, ClickArea.COMPANION);
map.put(ROOT_CONTENT_BEHAVIOR, RootContentBehavior.COMPANION);
map.put(ROOT_CONTENT_DESCRIPTION, RootContentDescription.COMPANION);
+
+ map.put(DRAW_ARC, DrawArc.COMPANION);
+ map.put(DRAW_BITMAP, DrawBitmap.COMPANION);
+ map.put(DRAW_CIRCLE, DrawCircle.COMPANION);
+ map.put(DRAW_LINE, DrawLine.COMPANION);
+ map.put(DRAW_OVAL, DrawOval.COMPANION);
+ map.put(DRAW_PATH, DrawPath.COMPANION);
+ map.put(DRAW_RECT, DrawRect.COMPANION);
+ map.put(DRAW_ROUND_RECT, DrawRoundRect.COMPANION);
+ map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath.COMPANION);
+ map.put(DRAW_TEXT_RUN, DrawTextRun.COMPANION);
+ map.put(DRAW_TWEEN_PATH, DrawTweenPath.COMPANION);
+ map.put(DATA_PATH, PathData.COMPANION);
+ map.put(PAINT_VALUES, PaintData.COMPANION);
+ map.put(MATRIX_RESTORE, MatrixRestore.COMPANION);
+ map.put(MATRIX_ROTATE, MatrixRotate.COMPANION);
+ map.put(MATRIX_SAVE, MatrixSave.COMPANION);
+ map.put(MATRIX_SCALE, MatrixScale.COMPANION);
+ map.put(MATRIX_SKEW, MatrixSkew.COMPANION);
+ map.put(MATRIX_TRANSLATE, MatrixTranslate.COMPANION);
+ map.put(CLIP_PATH, ClipPath.COMPANION);
+ map.put(CLIP_RECT, ClipRect.COMPANION);
+
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 6999cde..eece8ad52 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.widget.remotecompose.core;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
/**
* Specify an abstract paint context used by RemoteCompose commands to draw
*/
@@ -30,11 +32,74 @@
}
public abstract void drawBitmap(int imageId,
- int srcLeft, int srcTop, int srcRight, int srcBottom,
- int dstLeft, int dstTop, int dstRight, int dstBottom,
- int cdId);
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId);
public abstract void scale(float scaleX, float scaleY);
+
public abstract void translate(float translateX, float translateY);
+
+ public abstract void drawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle);
+
+ public abstract void drawBitmap(int id, float left, float top, float right, float bottom);
+
+ public abstract void drawCircle(float centerX, float centerY, float radius);
+
+ public abstract void drawLine(float x1, float y1, float x2, float y2);
+
+ public abstract void drawOval(float left, float top, float right, float bottom);
+
+ public abstract void drawPath(int id, float start, float end);
+
+ public abstract void drawRect(float left, float top, float right, float bottom);
+
+ public abstract void drawRoundRect(float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY);
+
+ public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset);
+
+ public abstract void drawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl);
+
+ public abstract void drawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop);
+
+ public abstract void applyPaint(PaintBundle mPaintData);
+
+ public abstract void mtrixScale(float scaleX, float scaleY, float centerX, float centerY);
+
+ public abstract void matrixTranslate(float translateX, float translateY);
+
+ public abstract void matrixSkew(float skewX, float skewY);
+
+ public abstract void matrixRotate(float rotate, float pivotX, float pivotY);
+
+ public abstract void matrixSave();
+
+ public abstract void matrixRestore();
+
+ public abstract void clipRect(float left, float top, float right, float bottom);
+
+ public abstract void clipPath(int pathId, int regionOp);
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index abda0c0..903dab4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -20,5 +20,8 @@
*/
public interface Platform {
byte[] imageToByteArray(Object image);
+ int getImageWidth(Object image);
+ int getImageHeight(Object image);
+ float[] pathToFloatArray(Object image);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index c34730f..c2e8131 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -17,12 +17,34 @@
import com.android.internal.widget.remotecompose.core.operations.BitmapData;
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawArc;
+import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
+import com.android.internal.widget.remotecompose.core.operations.DrawCircle;
+import com.android.internal.widget.remotecompose.core.operations.DrawLine;
+import com.android.internal.widget.remotecompose.core.operations.DrawOval;
+import com.android.internal.widget.remotecompose.core.operations.DrawPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
+import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
import com.android.internal.widget.remotecompose.core.operations.Header;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
+import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
import com.android.internal.widget.remotecompose.core.operations.TextData;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import java.io.File;
import java.io.FileInputStream;
@@ -82,10 +104,10 @@
/**
* Insert a header
*
- * @param width the width of the document in pixels
- * @param height the height of the document in pixels
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
* @param contentDescription content description of the document
- * @param capabilities bitmask indicating needed capabilities (unused for now)
+ * @param capabilities bitmask indicating needed capabilities (unused for now)
*/
public void header(int width, int height, String contentDescription, long capabilities) {
Header.COMPANION.apply(mBuffer, width, height, capabilities);
@@ -99,8 +121,8 @@
/**
* Insert a header
*
- * @param width the width of the document in pixels
- * @param height the height of the document in pixels
+ * @param width the width of the document in pixels
+ * @param height the height of the document in pixels
* @param contentDescription content description of the document
*/
public void header(int width, int height, String contentDescription) {
@@ -111,7 +133,7 @@
* Insert a bitmap
*
* @param image an opaque image that we'll add to the buffer
- * @param imageWidth the width of the image
+ * @param imageWidth the width of the image
* @param imageHeight the height of the image
* @param srcLeft left coordinate of the source area
* @param srcTop top coordinate of the source area
@@ -161,13 +183,13 @@
/**
* Add a click area to the document
*
- * @param id the id of the click area, reported in the click listener callback
+ * @param id the id of the click area, reported in the click listener callback
* @param contentDescription the content description of that click area (accessibility)
- * @param left left coordinate of the area bounds
- * @param top top coordinate of the area bounds
- * @param right right coordinate of the area bounds
- * @param bottom bottom coordinate of the area bounds
- * @param metadata associated metadata, user-provided
+ * @param left left coordinate of the area bounds
+ * @param top top coordinate of the area bounds
+ * @param right right coordinate of the area bounds
+ * @param bottom bottom coordinate of the area bounds
+ * @param metadata associated metadata, user-provided
*/
public void addClickArea(
int id,
@@ -193,35 +215,294 @@
/**
* Sets the way the player handles the content
*
- * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
* @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
- * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
- * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
- * the LAYOUT modes are:
- * - LAYOUT_MATCH_PARENT
- * - LAYOUT_WRAP_CONTENT
- * or adding an horizontal mode and a vertical mode:
- * - LAYOUT_HORIZONTAL_MATCH_PARENT
- * - LAYOUT_HORIZONTAL_WRAP_CONTENT
- * - LAYOUT_HORIZONTAL_FIXED
- * - LAYOUT_VERTICAL_MATCH_PARENT
- * - LAYOUT_VERTICAL_WRAP_CONTENT
- * - LAYOUT_VERTICAL_FIXED
- * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
*/
public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
RootContentBehavior.COMPANION.apply(mBuffer, scroll, alignment, sizing, mode);
}
+ /**
+ * add Drawing the specified arc, which will be scaled to fit inside the specified oval.
+ * <br>
+ * If the start angle is negative or >= 360, the start angle is treated as start angle modulo
+ * 360.
+ * <br>
+ * If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs
+ * slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is
+ * negative, the sweep angle is treated as sweep angle modulo 360
+ * <br>
+ * The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0
+ * degrees (3 o'clock on a watch.)
+ * <br>
+ *
+ * @param left left coordinate of oval used to define the shape and size of the arc
+ * @param top top coordinate of oval used to define the shape and size of the arc
+ * @param right right coordinate of oval used to define the shape and size of the arc
+ * @param bottom bottom coordinate of oval used to define the shape and size of the arc
+ * @param startAngle Starting angle (in degrees) where the arc begins
+ * @param sweepAngle Sweep angle (in degrees) measured clockwise
+ */
+ public void addDrawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ DrawArc.COMPANION.apply(mBuffer, left, top, right, bottom, startAngle, sweepAngle);
+ }
+
+ /**
+ * @param image The bitmap to be drawn
+ * @param left left coordinate of rectangle that the bitmap will be to fit into
+ * @param top top coordinate of rectangle that the bitmap will be to fit into
+ * @param right right coordinate of rectangle that the bitmap will be to fit into
+ * @param bottom bottom coordinate of rectangle that the bitmap will be to fit into
+ * @param contentDescription content description of the image
+ */
+ public void addDrawBitmap(Object image,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ String contentDescription) {
+ int imageId = mRemoteComposeState.dataGetId(image);
+ if (imageId == -1) {
+ imageId = mRemoteComposeState.cache(image);
+ byte[] data = mPlatform.imageToByteArray(image);
+ int imageWidth = mPlatform.getImageWidth(image);
+ int imageHeight = mPlatform.getImageHeight(image);
+
+ BitmapData.COMPANION.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+ }
+ int contentDescriptionId = 0;
+ if (contentDescription != null) {
+ contentDescriptionId = addText(contentDescription);
+ }
+ DrawBitmap.COMPANION.apply(
+ mBuffer, imageId, left, top, right, bottom, contentDescriptionId
+ );
+ }
+
+ /**
+ * Draw the specified circle using the specified paint. If radius is <= 0, then nothing will be
+ * drawn.
+ *
+ * @param centerX The x-coordinate of the center of the circle to be drawn
+ * @param centerY The y-coordinate of the center of the circle to be drawn
+ * @param radius The radius of the circle to be drawn
+ */
+ public void addDrawCircle(float centerX, float centerY, float radius) {
+ DrawCircle.COMPANION.apply(mBuffer, centerX, centerY, radius);
+ }
+
+ /**
+ * Draw a line segment with the specified start and stop x,y coordinates, using the specified
+ * paint.
+ *
+ * @param x1 The x-coordinate of the start point of the line
+ * @param y1 The y-coordinate of the start point of the line
+ * @param x2 The x-coordinate of the end point of the line
+ * @param y2 The y-coordinate of the end point of the line
+ */
+ public void addDrawLine(float x1, float y1, float x2, float y2) {
+ DrawLine.COMPANION.apply(mBuffer, x1, y1, x2, y2);
+ }
+
+ /**
+ * Draw the specified oval using the specified paint.
+ *
+ * @param left left coordinate of oval
+ * @param top top coordinate of oval
+ * @param right right coordinate of oval
+ * @param bottom bottom coordinate of oval
+ */
+ public void addDrawOval(float left, float top, float right, float bottom) {
+ DrawOval.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+ /**
+ * Draw the specified path
+ * <p>
+ * Note: path objects are not immutable
+ * modifying them and calling this will not change the drawing
+ *
+ * @param path The path to be drawn
+ */
+ public void addDrawPath(Object path) {
+ int id = mRemoteComposeState.dataGetId(path);
+ if (id == -1) { // never been seen before
+ id = addPathData(path);
+ }
+ addDrawPath(id);
+ }
+
+
+ /**
+ * Draw the specified path
+ *
+ * @param pathId
+ */
+ public void addDrawPath(int pathId) {
+ DrawPath.COMPANION.apply(mBuffer, pathId);
+ }
+
+ /**
+ * Draw the specified Rect
+ *
+ * @param left left coordinate of rectangle to be drawn
+ * @param top top coordinate of rectangle to be drawn
+ * @param right right coordinate of rectangle to be drawn
+ * @param bottom bottom coordinate of rectangle to be drawn
+ */
+ public void addDrawRect(float left, float top, float right, float bottom) {
+ DrawRect.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+ /**
+ * Draw the specified round-rect
+ *
+ * @param left left coordinate of rectangle to be drawn
+ * @param top left coordinate of rectangle to be drawn
+ * @param right left coordinate of rectangle to be drawn
+ * @param bottom left coordinate of rectangle to be drawn
+ * @param radiusX The x-radius of the oval used to round the corners
+ * @param radiusY The y-radius of the oval used to round the corners
+ */
+ public void addDrawRoundRect(float left, float top, float right, float bottom,
+ float radiusX, float radiusY) {
+ DrawRoundRect.COMPANION.apply(mBuffer, left, top, right, bottom, radiusX, radiusY);
+ }
+
+ /**
+ * Draw the text, with origin at (x,y) along the specified path.
+ *
+ * @param text The text to be drawn
+ * @param path The path the text should follow for its baseline
+ * @param hOffset The distance along the path to add to the text's starting position
+ * @param vOffset The distance above(-) or below(+) the path to position the text
+ */
+ public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) {
+ int pathId = mRemoteComposeState.dataGetId(path);
+ if (pathId == -1) { // never been seen before
+ pathId = addPathData(path);
+ }
+ int textId = addText(text);
+ DrawTextOnPath.COMPANION.apply(mBuffer, textId, pathId, hOffset, vOffset);
+ }
+
+ /**
+ * Draw the text, with origin at (x,y). The origin is interpreted
+ * based on the Align setting in the paint.
+ *
+ * @param text The text to be drawn
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param contextStart
+ * @param contextEnd
+ * @param x The x-coordinate of the origin of the text being drawn
+ * @param y The y-coordinate of the baseline of the text being drawn
+ * @param rtl Draw RTTL
+ */
+ public void addDrawTextRun(String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ int textId = addText(text);
+ DrawTextRun.COMPANION.apply(
+ mBuffer, textId, start, end,
+ contextStart, contextEnd, x, y, rtl);
+ }
+
+ /**
+ * draw an interpolation between two paths that have the same pattern
+ * <p>
+ * Warning paths objects are not immutable and this is not taken into consideration
+ *
+ * @param path1 The path1 to be drawn between
+ * @param path2 The path2 to be drawn between
+ * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
+ * @param start The start of the subrange of paths to draw 0 = start form start 0.5 is half way
+ * @param stop The end of the subrange of paths to draw 1 = end at the end 0.5 is end half way
+ */
+ public void addDrawTweenPath(Object path1,
+ Object path2,
+ float tween,
+ float start,
+ float stop) {
+ int path1Id = mRemoteComposeState.dataGetId(path1);
+ if (path1Id == -1) { // never been seen before
+ path1Id = addPathData(path1);
+ }
+ int path2Id = mRemoteComposeState.dataGetId(path2);
+ if (path2Id == -1) { // never been seen before
+ path2Id = addPathData(path2);
+ }
+ addDrawTweenPath(path1Id, path2Id, tween, start, stop);
+ }
+
+ /**
+ * draw an interpolation between two paths that have the same pattern
+ *
+ * @param path1Id The path1 to be drawn between
+ * @param path2Id The path2 to be drawn between
+ * @param tween The ratio of path1 and path2 to 0 = all path 1, 1 = all path2
+ * @param start The start of the subrange of paths to draw 0 = start form start .5 is 1/2 way
+ * @param stop The end of the subrange of paths to draw 1 = end at the end .5 is end 1/2 way
+ */
+ public void addDrawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ DrawTweenPath.COMPANION.apply(
+ mBuffer, path1Id, path2Id,
+ tween, start, stop);
+ }
+
+ /**
+ * Add a path object
+ *
+ * @param path
+ * @return the id of the path on the wire
+ */
+ public int addPathData(Object path) {
+ float[] pathData = mPlatform.pathToFloatArray(path);
+ int id = mRemoteComposeState.cache(path);
+ PathData.COMPANION.apply(mBuffer, id, pathData);
+ return id;
+ }
+
+ public void addPaint(PaintBundle paint) {
+ PaintData.COMPANION.apply(mBuffer, paint);
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
public void inflateFromBuffer(ArrayList<Operation> operations) {
mBuffer.setIndex(0);
while (mBuffer.available()) {
int opId = mBuffer.readByte();
+ System.out.println(">>> " + opId);
CompanionOperation operation = Operations.map.get(opId);
if (operation == null) {
- throw new RuntimeException("Unknown operation encountered");
+ throw new RuntimeException("Unknown operation encountered " + opId);
}
operation.read(mBuffer, operations);
}
@@ -259,7 +540,7 @@
}
public static RemoteComposeBuffer fromInputStream(InputStream inputStream,
- RemoteComposeState remoteComposeState) {
+ RemoteComposeState remoteComposeState) {
RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
read(inputStream, buffer);
return buffer;
@@ -318,5 +599,86 @@
}
}
+ /**
+ * add a Pre-concat the current matrix with the specified skew.
+ *
+ * @param skewX The amount to skew in X
+ * @param skewY The amount to skew in Y
+ */
+ public void addMatrixSkew(float skewX, float skewY) {
+ MatrixSkew.COMPANION.apply(mBuffer, skewX, skewY);
+ }
+
+ /**
+ * This call balances a previous call to save(), and is used to remove all
+ * modifications to the matrix/clip state since the last save call.
+ * Do not call restore() more times than save() was called.
+ */
+ public void addMatrixRestore() {
+ MatrixRestore.COMPANION.apply(mBuffer);
+ }
+
+ /**
+ * Add a saves the current matrix and clip onto a private stack.
+ * <p>
+ * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
+ * clipPath will all operate as usual, but when the balancing call to
+ * restore() is made, those calls will be forgotten, and the settings that
+ * existed before the save() will be reinstated.
+ */
+ public void addMatrixSave() {
+ MatrixSave.COMPANION.apply(mBuffer);
+ }
+
+ /**
+ * add a pre-concat the current matrix with the specified rotation.
+ *
+ * @param angle The amount to rotate, in degrees
+ * @param centerX The x-coord for the pivot point (unchanged by the rotation)
+ * @param centerY The y-coord for the pivot point (unchanged by the rotation)
+ */
+ public void addMatrixRotate(float angle, float centerX, float centerY) {
+ MatrixRotate.COMPANION.apply(mBuffer, angle, centerX, centerY);
+ }
+
+ /**
+ * add a Pre-concat to the current matrix with the specified translation
+ *
+ * @param dx The distance to translate in X
+ * @param dy The distance to translate in Y
+ */
+ public void addMatrixTranslate(float dx, float dy) {
+ MatrixTranslate.COMPANION.apply(mBuffer, dx, dy);
+ }
+
+ /**
+ * Add a pre-concat of the current matrix with the specified scale.
+ *
+ * @param scaleX The amount to scale in X
+ * @param scaleY The amount to scale in Y
+ */
+ public void addMatrixScale(float scaleX, float scaleY) {
+ MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN);
+ }
+
+ /**
+ * Add a pre-concat of the current matrix with the specified scale.
+ *
+ * @param scaleX The amount to scale in X
+ * @param scaleY The amount to scale in Y
+ * @param centerX The x-coord for the pivot point (unchanged by the scale)
+ * @param centerY The y-coord for the pivot point (unchanged by the scale)
+ */
+ public void addMatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
+ MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY);
+ }
+
+ public void addClipPath(int pathId) {
+ ClipPath.COMPANION.apply(mBuffer, pathId);
+ }
+
+ public void addClipRect(float left, float top, float right, float bottom) {
+ ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 1b7c6fd..d16cbc5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -37,6 +37,8 @@
public float mWidth = 0f;
public float mHeight = 0f;
+ public abstract void loadPathData(int instanceId, float[] floatPath);
+
/**
* The context can be used in a few different mode, allowing operations to skip being executed:
* - UNSET : all operations will get executed
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 7c9fda5..fc3202e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -37,7 +37,7 @@
this(BUFFER_SIZE);
}
- public void resize(int need) {
+ private void resize(int need) {
if (mSize + need >= mMaxSize) {
mMaxSize = Math.max(mMaxSize * 2, mSize + need);
mBuffer = Arrays.copyOf(mBuffer, mMaxSize);
@@ -120,7 +120,7 @@
}
public int readByte() {
- byte value = mBuffer[mIndex];
+ int value = 0xFF & mBuffer[mIndex];
mIndex++;
return value;
}
@@ -130,6 +130,14 @@
int v2 = (mBuffer[mIndex++] & 0xFF) << 0;
return v1 + v2;
}
+ public int peekInt() {
+ int tmp = mIndex;
+ int v1 = (mBuffer[tmp++] & 0xFF) << 24;
+ int v2 = (mBuffer[tmp++] & 0xFF) << 16;
+ int v3 = (mBuffer[tmp++] & 0xFF) << 8;
+ int v4 = (mBuffer[tmp++] & 0xFF) << 0;
+ return v1 + v2 + v3 + v4;
+ }
public int readInt() {
int v1 = (mBuffer[mIndex++] & 0xFF) << 24;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
new file mode 100644
index 0000000..8d4a787
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class ClipPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mId;
+ int mRegionOp;
+
+ public ClipPath(int pathId, int regionOp) {
+ mId = pathId;
+ mRegionOp = regionOp;
+ }
+
+ public static final int REPLACE = Companion.PATH_CLIP_REPLACE;
+ public static final int DIFFERENCE = Companion.PATH_CLIP_DIFFERENCE;
+ public static final int INTERSECT = Companion.PATH_CLIP_INTERSECT;
+ public static final int UNION = Companion.PATH_CLIP_UNION;
+ public static final int XOR = Companion.PATH_CLIP_XOR;
+ public static final int REVERSE_DIFFERENCE = Companion.PATH_CLIP_REVERSE_DIFFERENCE;
+ public static final int UNDEFINED = Companion.PATH_CLIP_UNDEFINED;
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId);
+ }
+
+ @Override
+ public String toString() {
+ return "ClipPath " + mId + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ public static final int PATH_CLIP_REPLACE = 0;
+ public static final int PATH_CLIP_DIFFERENCE = 1;
+ public static final int PATH_CLIP_INTERSECT = 2;
+ public static final int PATH_CLIP_UNION = 3;
+ public static final int PATH_CLIP_XOR = 4;
+ public static final int PATH_CLIP_REVERSE_DIFFERENCE = 5;
+ public static final int PATH_CLIP_UNDEFINED = 6;
+
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int pack = buffer.readInt();
+ int id = pack & 0xFFFFF;
+ int regionOp = pack >> 24;
+ ClipPath op = new ClipPath(id, regionOp);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "ClipPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.CLIP_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id) {
+ buffer.start(Operations.CLIP_PATH);
+ buffer.writeInt(id);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipPath(mId, mRegionOp);
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
new file mode 100644
index 0000000..803618a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class ClipRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public ClipRect(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "ClipRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ ClipRect op = new ClipRect(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "ClipRect";
+ }
+
+ @Override
+ public int id() {
+ return Operations.CLIP_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.CLIP_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipRect(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
new file mode 100644
index 0000000..e829975
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawArc extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ float mStartAngle;
+ float mSweepAngle;
+
+ public DrawArc(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mStartAngle = startAngle;
+ mSweepAngle = sweepAngle;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mStartAngle,
+ mSweepAngle);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + " "
+ + "- " + mStartAngle + " " + mSweepAngle + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ float mStartAngle = buffer.readFloat();
+ float mSweepAngle = buffer.readFloat();
+ DrawArc op = new DrawArc(sLeft, srcTop, srcRight, srcBottom,
+ mStartAngle, mSweepAngle);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawArc";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_ARC;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ buffer.start(Operations.DRAW_ARC);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeFloat(startAngle);
+ buffer.writeFloat(sweepAngle);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawArc(mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mStartAngle,
+ mSweepAngle);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
new file mode 100644
index 0000000..2e971f5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawBitmap extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ int mId;
+ int mDescriptionId = 0;
+
+ public DrawBitmap(
+ int imageId,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int descriptionId) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mId = imageId;
+ mDescriptionId = descriptionId;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawBitmap (desc=" + mDescriptionId + ")" + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int id = buffer.readInt();
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ int discriptionId = buffer.readInt();
+
+ DrawBitmap op = new DrawBitmap(id, sLeft, srcTop, srcRight, srcBottom, discriptionId);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_BITMAP;
+ }
+
+ public void apply(WireBuffer buffer,
+ int id,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ int descriptionId) {
+ buffer.start(Operations.DRAW_BITMAP);
+ buffer.writeInt(id);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeInt(descriptionId);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawBitmap(mId, mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 3fbdf94..c2a56e7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -76,7 +76,8 @@
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
@@ -89,9 +90,9 @@
}
public void apply(WireBuffer buffer, int imageId,
- int srcLeft, int srcTop, int srcRight, int srcBottom,
- int dstLeft, int dstTop, int dstRight, int dstBottom,
- int cdId) {
+ int srcLeft, int srcTop, int srcRight, int srcBottom,
+ int dstLeft, int dstTop, int dstRight, int dstBottom,
+ int cdId) {
buffer.start(Operations.DRAW_BITMAP_INT);
buffer.writeInt(imageId);
buffer.writeInt(srcLeft);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
new file mode 100644
index 0000000..9ce754d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawCircle extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mCenterX;
+ float mCenterY;
+ float mRadius;
+
+ public DrawCircle(float centerX, float centerY, float radius) {
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mRadius = radius;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mCenterX,
+ mCenterY,
+ mRadius);
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float centerX = buffer.readFloat();
+ float centerY = buffer.readFloat();
+ float radius = buffer.readFloat();
+
+ DrawCircle op = new DrawCircle(centerX, centerY, radius);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public int id() {
+ return 0;
+ }
+
+ public void apply(WireBuffer buffer, float centerX, float centerY, float radius) {
+ buffer.start(Operations.DRAW_CIRCLE);
+ buffer.writeFloat(centerX);
+ buffer.writeFloat(centerY);
+ buffer.writeFloat(radius);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawCircle(mCenterX,
+ mCenterY,
+ mRadius);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
new file mode 100644
index 0000000..c7a8315
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawLine extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mX1;
+ float mY1;
+ float mX2;
+ float mY2;
+
+ public DrawLine(
+ float x1,
+ float y1,
+ float x2,
+ float y2) {
+ mX1 = x1;
+ mY1 = y1;
+ mX2 = x2;
+ mY2 = y2;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX1,
+ mY1,
+ mX2,
+ mY2);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mX1 + " " + mY1
+ + " " + mX2 + " " + mY2 + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x1 = buffer.readFloat();
+ float y1 = buffer.readFloat();
+ float x2 = buffer.readFloat();
+ float y2 = buffer.readFloat();
+
+ DrawLine op = new DrawLine(x1, y1, x2, y2);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawLine";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_LINE;
+ }
+
+ public void apply(WireBuffer buffer,
+ float x1,
+ float y1,
+ float x2,
+ float y2) {
+ buffer.start(Operations.DRAW_LINE);
+ buffer.writeFloat(x1);
+ buffer.writeFloat(y1);
+ buffer.writeFloat(x2);
+ buffer.writeFloat(y2);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawLine(mX1,
+ mY1,
+ mX2,
+ mY2);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
new file mode 100644
index 0000000..7143753
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawOval extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+
+ public DrawOval(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawOval " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ DrawOval op = new DrawOval(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_OVAL;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.DRAW_OVAL);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawOval(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
new file mode 100644
index 0000000..7b8a9e9
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mId;
+ float mStart = 0;
+ float mEnd = 1;
+
+ public DrawPath(int pathId) {
+ mId = pathId;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mId);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawPath " + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int id = buffer.readInt();
+ DrawPath op = new DrawPath(id);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id) {
+ buffer.start(Operations.DRAW_PATH);
+ buffer.writeInt(id);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawPath(mId, mStart, mEnd);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
new file mode 100644
index 0000000..4775241
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public DrawRect(
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+
+ DrawRect op = new DrawRect(sLeft, srcTop, srcRight, srcBottom);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawRect";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ buffer.start(Operations.DRAW_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawRect(mLeft,
+ mTop,
+ mRight,
+ mBottom);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
new file mode 100644
index 0000000..8da16e7
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawRoundRect extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+ float mRadiusX;
+ float mRadiusY;
+
+ public DrawRoundRect(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mRadiusX = radiusX;
+ mRadiusY = radiusY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom, mRadiusX, mRadiusY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawRoundRect " + mLeft + " " + mTop
+ + " " + mRight + " " + mBottom
+ + " (" + mRadiusX + " " + mRadiusY + ");";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float sLeft = buffer.readFloat();
+ float srcTop = buffer.readFloat();
+ float srcRight = buffer.readFloat();
+ float srcBottom = buffer.readFloat();
+ float srcRadiusX = buffer.readFloat();
+ float srcRadiusY = buffer.readFloat();
+
+ DrawRoundRect op = new DrawRoundRect(sLeft, srcTop, srcRight,
+ srcBottom, srcRadiusX, srcRadiusY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawOval";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_ROUND_RECT;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ buffer.start(Operations.DRAW_ROUND_RECT);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ buffer.writeFloat(radiusX);
+ buffer.writeFloat(radiusY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawRoundRect(mLeft,
+ mTop,
+ mRight,
+ mBottom,
+ mRadiusX,
+ mRadiusY
+ );
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
new file mode 100644
index 0000000..1856e30
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTextOnPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mPathId;
+ public int mTextId;
+ float mVOffset;
+ float mHOffset;
+
+ public DrawTextOnPath(int textId, int pathId, float hOffset, float vOffset) {
+ mPathId = pathId;
+ mTextId = textId;
+ mHOffset = vOffset;
+ mVOffset = hOffset;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTextId, mPathId, mHOffset, mVOffset);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawTextOnPath " + " " + mPathId + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int textId = buffer.readInt();
+ int pathId = buffer.readInt();
+ float hOffset = buffer.readFloat();
+ float vOffset = buffer.readFloat();
+ DrawTextOnPath op = new DrawTextOnPath(textId, pathId, hOffset, vOffset);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawTextOnPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_TEXT_ON_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
+ buffer.start(Operations.DRAW_TEXT_ON_PATH);
+ buffer.writeInt(textId);
+ buffer.writeInt(pathId);
+ buffer.writeFloat(hOffset);
+ buffer.writeFloat(vOffset);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTextOnPath(mTextId, mPathId, mHOffset, mVOffset);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
new file mode 100644
index 0000000..a099252
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTextRun extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ int mTextID;
+ int mStart = 0;
+ int mEnd = 0;
+ int mContextStart = 0;
+ int mContextEnd = 0;
+ float mX = 0f;
+ float mY = 0f;
+ boolean mRtl = false;
+
+ public DrawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ mTextID = textID;
+ mStart = start;
+ mEnd = end;
+ mContextStart = contextStart;
+ mContextEnd = contextEnd;
+ mX = x;
+ mY = y;
+ mRtl = rtl;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int text = buffer.readInt();
+ int start = buffer.readInt();
+ int end = buffer.readInt();
+ int contextStart = buffer.readInt();
+ int contextEnd = buffer.readInt();
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ boolean rtl = buffer.readBoolean();
+ DrawTextRun op = new DrawTextRun(text, start, end, contextStart, contextEnd, x, y, rtl);
+
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "";
+ }
+
+ @Override
+ public int id() {
+ return 0;
+ }
+
+ public void apply(WireBuffer buffer,
+ int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ buffer.start(Operations.DRAW_TEXT_RUN);
+ buffer.writeInt(textID);
+ buffer.writeInt(start);
+ buffer.writeInt(end);
+ buffer.writeInt(contextStart);
+ buffer.writeInt(contextEnd);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeBoolean(rtl);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
new file mode 100644
index 0000000..ef0a4ad
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class DrawTweenPath extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mTween;
+ float mStart;
+ float mStop;
+ int mPath1Id;
+ int mPath2Id;
+
+ public DrawTweenPath(
+ int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ mTween = tween;
+ mStart = start;
+ mStop = stop;
+ mPath1Id = path1Id;
+ mPath2Id = path2Id;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mPath1Id,
+ mPath2Id,
+ mTween,
+ mStart,
+ mStop);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawTweenPath " + mPath1Id + " " + mPath2Id
+ + " " + mTween + " " + mStart + " "
+ + "- " + mStop + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int path1Id = buffer.readInt();
+ int path2Id = buffer.readInt();
+ float tween = buffer.readFloat();
+ float start = buffer.readFloat();
+ float stop = buffer.readFloat();
+ DrawTweenPath op = new DrawTweenPath(path1Id, path2Id,
+ tween, start, stop);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "DrawTweenPath";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DRAW_TWEEN_PATH;
+ }
+
+ public void apply(WireBuffer buffer,
+ int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float stop) {
+ buffer.start(Operations.DRAW_TWEEN_PATH);
+ buffer.writeInt(path1Id);
+ buffer.writeInt(path2Id);
+ buffer.writeFloat(tween);
+ buffer.writeFloat(start);
+ buffer.writeFloat(stop);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.drawTweenPath(mPath1Id,
+ mPath2Id,
+ mTween,
+ mStart,
+ mStop);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index eca43c5..aabed15 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -26,11 +26,11 @@
/**
* Describe some basic information for a RemoteCompose document
- *
+ * <p>
* It encodes the version of the document (following semantic versioning) as well
* as the dimensions of the document in pixels.
*/
-public class Header implements RemoteComposeOperation {
+public class Header implements RemoteComposeOperation {
public static final int MAJOR_VERSION = 0;
public static final int MINOR_VERSION = 1;
public static final int PATCH_VERSION = 0;
@@ -89,7 +89,8 @@
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
new file mode 100644
index 0000000..482e0e2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixRestore extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+
+ public MatrixRestore() {
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixRestore;";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+
+ MatrixRestore op = new MatrixRestore();
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "MatrixRestore";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_RESTORE;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MATRIX_RESTORE);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixRestore();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
new file mode 100644
index 0000000..d6c89e0
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixRotate extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mRotate, mPivotX, mPivotY;
+
+ public MatrixRotate(float rotate, float pivotX, float pivotY) {
+ mRotate = rotate;
+ mPivotX = pivotX;
+ mPivotY = pivotY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mRotate, mPivotX, mPivotY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mRotate + ", " + mPivotX + ", " + mPivotY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float rotate = buffer.readFloat();
+ float pivotX = buffer.readFloat();
+ float pivotY = buffer.readFloat();
+ MatrixRotate op = new MatrixRotate(rotate, pivotX, pivotY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_ROTATE;
+ }
+
+ public void apply(WireBuffer buffer, float rotate, float pivotX, float pivotY) {
+ buffer.start(Operations.MATRIX_ROTATE);
+ buffer.writeFloat(rotate);
+ buffer.writeFloat(pivotX);
+ buffer.writeFloat(pivotY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixRotate(mRotate, mPivotX, mPivotY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
new file mode 100644
index 0000000..d3d5bfb
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixSave extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+
+ public MatrixSave() {
+
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixSave;";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+
+ MatrixSave op = new MatrixSave();
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SAVE;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MATRIX_SAVE);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixSave();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
new file mode 100644
index 0000000..28aa68dd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixScale extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mScaleX, mScaleY;
+ float mCenterX, mCenterY;
+
+ public MatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
+ mScaleX = scaleX;
+ mScaleY = scaleY;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mScaleX, mScaleY, mCenterX, mCenterY);
+ }
+
+ @Override
+ public String toString() {
+ return "MatrixScale " + mScaleY + ", " + mScaleY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float scaleX = buffer.readFloat();
+ float scaleY = buffer.readFloat();
+ float centerX = buffer.readFloat();
+ float centerY = buffer.readFloat();
+ MatrixScale op = new MatrixScale(scaleX, scaleY, centerX, centerY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SCALE;
+ }
+
+ public void apply(WireBuffer buffer, float scaleX, float scaleY,
+ float centerX, float centerY) {
+ buffer.start(Operations.MATRIX_SCALE);
+ buffer.writeFloat(scaleX);
+ buffer.writeFloat(scaleY);
+ buffer.writeFloat(centerX);
+ buffer.writeFloat(centerY);
+
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.mtrixScale(mScaleX, mScaleY, mCenterX, mCenterY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
new file mode 100644
index 0000000..a388899
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixSkew extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mSkewX, mSkewY;
+
+ public MatrixSkew(float skewX, float skewY) {
+ mSkewX = skewX;
+ mSkewY = skewY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mSkewX, mSkewY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mSkewY + ", " + mSkewY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float skewX = buffer.readFloat();
+ float skewY = buffer.readFloat();
+ MatrixSkew op = new MatrixSkew(skewX, skewY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_SKEW;
+ }
+
+ public void apply(WireBuffer buffer, float skewX, float skewY) {
+ buffer.start(Operations.MATRIX_SKEW);
+ buffer.writeFloat(skewX);
+ buffer.writeFloat(skewY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixSkew(mSkewX, mSkewY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
new file mode 100644
index 0000000..3298752
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class MatrixTranslate extends PaintOperation {
+ public static final Companion COMPANION = new Companion();
+ float mTranslateX, mTranslateY;
+
+ public MatrixTranslate(float translateX, float translateY) {
+ mTranslateX = translateX;
+ mTranslateY = translateY;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mTranslateX, mTranslateY);
+ }
+
+ @Override
+ public String toString() {
+ return "DrawArc " + mTranslateY + ", " + mTranslateY + ";";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float translateX = buffer.readFloat();
+ float translateY = buffer.readFloat();
+ MatrixTranslate op = new MatrixTranslate(translateX, translateY);
+ operations.add(op);
+ }
+
+ @Override
+ public String name() {
+ return "Matrix";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MATRIX_TRANSLATE;
+ }
+
+ public void apply(WireBuffer buffer, float translateX, float translateY) {
+ buffer.start(Operations.MATRIX_TRANSLATE);
+ buffer.writeFloat(translateX);
+ buffer.writeFloat(translateY);
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.matrixTranslate(mTranslateX, mTranslateY);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
new file mode 100644
index 0000000..e5683ec
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
+import java.util.List;
+
+public class PaintData extends PaintOperation {
+ public PaintBundle mPaintData = new PaintBundle();
+ public static final Companion COMPANION = new Companion();
+ public static final int MAX_STRING_SIZE = 4000;
+
+ public PaintData() {
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mPaintData);
+ }
+
+ @Override
+ public String toString() {
+ return "PaintData " + "\"" + mPaintData + "\"";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public String name() {
+ return "TextData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.PAINT_VALUES;
+ }
+
+ public void apply(WireBuffer buffer, PaintBundle paintBundle) {
+ buffer.start(Operations.PAINT_VALUES);
+ paintBundle.writeBundle(buffer);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ PaintData data = new PaintData();
+ data.mPaintData.readBundle(buffer);
+ operations.add(data);
+ }
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.applyPaint(mPaintData);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
new file mode 100644
index 0000000..2646b27
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+public class PathData implements Operation {
+ public static final Companion COMPANION = new Companion();
+ int mInstanceId;
+ float[] mRef;
+ float[] mFloatPath;
+ float[] mRetFloats;
+
+ PathData(int instanceId, float[] floatPath) {
+ mInstanceId = instanceId;
+ mFloatPath = floatPath;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mInstanceId, mFloatPath);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return pathString(mFloatPath);
+ }
+
+ public float[] getFloatPath(PaintContext context) {
+ float[] ret = mRetFloats; // Assume retFloats is declared elsewhere
+ if (ret == null) {
+ return mFloatPath; // Assume floatPath is declared elsewhere
+ }
+ float[] localRef = mRef; // Assume ref is of type Float[]
+ if (localRef == null) {
+ for (int i = 0; i < mFloatPath.length; i++) {
+ ret[i] = mFloatPath[i];
+ }
+ } else {
+ for (int i = 0; i < mFloatPath.length; i++) {
+ float lr = localRef[i];
+ if (Float.isNaN(lr)) {
+ ret[i] = Utils.getActualValue(lr);
+ } else {
+ ret[i] = mFloatPath[i];
+ }
+ }
+ }
+ return ret;
+ }
+
+ public static final int MOVE = 10;
+ public static final int LINE = 11;
+ public static final int QUADRATIC = 12;
+ public static final int CONIC = 13;
+ public static final int CUBIC = 14;
+ public static final int CLOSE = 15;
+ public static final int DONE = 16;
+ public static final float MOVE_NAN = Utils.asNan(MOVE);
+ public static final float LINE_NAN = Utils.asNan(LINE);
+ public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
+ public static final float CONIC_NAN = Utils.asNan(CONIC);
+ public static final float CUBIC_NAN = Utils.asNan(CUBIC);
+ public static final float CLOSE_NAN = Utils.asNan(CLOSE);
+ public static final float DONE_NAN = Utils.asNan(DONE);
+
+ public static class Companion implements CompanionOperation {
+
+ private Companion() {
+ }
+
+ @Override
+ public String name() {
+ return "BitmapData";
+ }
+
+ @Override
+ public int id() {
+ return Operations.DATA_PATH;
+ }
+
+ public void apply(WireBuffer buffer, int id, float[] data) {
+ buffer.start(Operations.DATA_PATH);
+ buffer.writeInt(id);
+ buffer.writeInt(data.length);
+ for (int i = 0; i < data.length; i++) {
+ buffer.writeFloat(data[i]);
+ }
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int imageId = buffer.readInt();
+ int len = buffer.readInt();
+ float[] data = new float[len];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = buffer.readFloat();
+ }
+ operations.add(new PathData(imageId, data));
+ }
+ }
+
+ public static String pathString(float[] path) {
+ if (path == null) {
+ return "null";
+ }
+ StringBuilder str = new StringBuilder();
+ for (int i = 0; i < path.length; i++) {
+ if (i != 0) {
+ str.append(" ");
+ }
+ if (Float.isNaN(path[i])) {
+ int id = Utils.idFromNan(path[i]); // Assume idFromNan is defined elsewhere
+ if (id <= DONE) { // Assume DONE is a constant
+ switch (id) {
+ case MOVE:
+ str.append("M");
+ break;
+ case LINE:
+ str.append("L");
+ break;
+ case QUADRATIC:
+ str.append("Q");
+ break;
+ case CONIC:
+ str.append("R");
+ break;
+ case CUBIC:
+ str.append("C");
+ break;
+ case CLOSE:
+ str.append("Z");
+ break;
+ case DONE:
+ str.append(".");
+ break;
+ default:
+ str.append("X");
+ break;
+ }
+ } else {
+ str.append("(" + id + ")");
+ }
+ } else {
+ str.append(path[i]);
+ }
+ }
+ return str.toString();
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.loadPathData(mInstanceId, mFloatPath);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index ad4caea..6d924eb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -28,7 +28,7 @@
/**
* Describe some basic information for a RemoteCompose document
- *
+ * <p>
* It encodes the version of the document (following semantic versioning) as well
* as the dimensions of the document in pixels.
*/
@@ -100,21 +100,21 @@
/**
* Sets the way the player handles the content
*
- * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+ * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
* @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
- * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
- * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
- * the LAYOUT modes are:
- * - LAYOUT_MATCH_PARENT
- * - LAYOUT_WRAP_CONTENT
- * or adding an horizontal mode and a vertical mode:
- * - LAYOUT_HORIZONTAL_MATCH_PARENT
- * - LAYOUT_HORIZONTAL_WRAP_CONTENT
- * - LAYOUT_HORIZONTAL_FIXED
- * - LAYOUT_VERTICAL_MATCH_PARENT
- * - LAYOUT_VERTICAL_WRAP_CONTENT
- * - LAYOUT_VERTICAL_FIXED
- * The LAYOUT_*_FIXED modes will use the intrinsic document size
+ * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+ * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
+ * the LAYOUT modes are:
+ * - LAYOUT_MATCH_PARENT
+ * - LAYOUT_WRAP_CONTENT
+ * or adding an horizontal mode and a vertical mode:
+ * - LAYOUT_HORIZONTAL_MATCH_PARENT
+ * - LAYOUT_HORIZONTAL_WRAP_CONTENT
+ * - LAYOUT_HORIZONTAL_FIXED
+ * - LAYOUT_VERTICAL_MATCH_PARENT
+ * - LAYOUT_VERTICAL_WRAP_CONTENT
+ * - LAYOUT_VERTICAL_FIXED
+ * The LAYOUT_*_FIXED modes will use the intrinsic document size
*/
public RootContentBehavior(int scroll, int alignment, int sizing, int mode) {
switch (scroll) {
@@ -149,10 +149,12 @@
switch (sizing) {
case SIZING_LAYOUT: {
Log.e(TAG, "sizing_layout is not yet supported");
- } break;
+ }
+ break;
case SIZING_SCALE: {
mSizing = sizing;
- } break;
+ }
+ break;
default: {
Log.e(TAG, "incorrect sizing value " + sizing);
}
@@ -200,7 +202,8 @@
}
public static class Companion implements CompanionOperation {
- private Companion() {}
+ private Companion() {
+ }
@Override
public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
new file mode 100644
index 0000000..00e2f20
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+public class Utils {
+ public static float asNan(int v) {
+ return Float.intBitsToFloat(v | -0x800000);
+ }
+
+ public static int idFromNan(float value) {
+ int b = Float.floatToRawIntBits(value);
+ return b & 0xFFFFF;
+ }
+
+ public static float getActualValue(float lr) {
+ return 0;
+ }
+
+ String getFloatString(float value) {
+ if (Float.isNaN(value)) {
+ int id = idFromNan(value);
+ if (id > 0) {
+ return "NaN(" + id + ")";
+ }
+ }
+ return "" + value;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
new file mode 100644
index 0000000..8abb0bf
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.Arrays;
+
+public class PaintBundle {
+ int[] mArray = new int[200];
+ int mPos = 0;
+
+ public void applyPaintChange(PaintChanges p) {
+ int i = 0;
+ int mask = 0;
+ while (i < mPos) {
+ int cmd = mArray[i++];
+ mask = mask | (1 << (cmd - 1));
+ switch (cmd & 0xFFFF) {
+ case TEXT_SIZE: {
+ p.setTextSize(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case TYPEFACE:
+ int style = (cmd >> 16);
+ int weight = style & 0x3ff;
+ boolean italic = (style >> 10) > 0;
+ int font_type = mArray[i++];
+
+ p.setTypeFace(font_type, weight, italic);
+ break;
+ case COLOR: {
+ p.setColor(mArray[i++]);
+ break;
+ }
+ case STROKE_WIDTH: {
+ p.setStrokeWidth(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case STROKE_MITER: {
+ p.setStrokeMiter(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ case STROKE_CAP: {
+ p.setStrokeCap(cmd >> 16);
+ break;
+ }
+ case STYLE: {
+ p.setStyle(cmd >> 16);
+ break;
+ }
+ case SHADER: {
+ break;
+ }
+ case STROKE_JOIN: {
+ p.setStrokeJoin(cmd >> 16);
+ break;
+ }
+ case IMAGE_FILTER_QUALITY: {
+ p.setImageFilterQuality(cmd >> 16);
+ break;
+ }
+ case BLEND_MODE: {
+ p.setBlendMode(cmd >> 16);
+ break;
+ }
+ case FILTER_BITMAP: {
+ p.setFilterBitmap(!((cmd >> 16) == 0));
+ break;
+ }
+
+ case GRADIENT: {
+ i = callSetGradient(cmd, mArray, i, p);
+ break;
+ }
+ case COLOR_FILTER: {
+ p.setColorFilter(mArray[i++], cmd >> 16);
+ break;
+ }
+ case ALPHA: {
+ p.setAlpha(Float.intBitsToFloat(mArray[i++]));
+ break;
+ }
+ }
+ }
+
+ mask = (~mask) & PaintChanges.VALID_BITS;
+
+ p.clear(mask);
+ }
+
+ private String toName(int id) {
+ switch (id) {
+ case TEXT_SIZE:
+ return "TEXT_SIZE";
+
+ case COLOR:
+ return "COLOR";
+ case STROKE_WIDTH:
+ return "STROKE_WIDTH";
+ case STROKE_MITER:
+ return "STROKE_MITER";
+ case TYPEFACE:
+ return "TYPEFACE";
+ case STROKE_CAP:
+ return "CAP";
+ case STYLE:
+ return "STYLE";
+ case SHADER:
+ return "SHADER";
+ case IMAGE_FILTER_QUALITY:
+ return "IMAGE_FILTER_QUALITY";
+ case BLEND_MODE:
+ return "BLEND_MODE";
+ case FILTER_BITMAP:
+ return "FILTER_BITMAP";
+ case GRADIENT:
+ return "GRADIENT_LINEAR";
+ case ALPHA:
+ return "ALPHA";
+ case COLOR_FILTER:
+ return "COLOR_FILTER";
+
+ }
+ return "????" + id + "????";
+ }
+
+ private static String colorInt(int color) {
+ String str = "000000000000" + Integer.toHexString(color);
+ return "0x" + str.substring(str.length() - 8);
+ }
+
+ private static String colorInt(int[] color) {
+ String str = "[";
+ for (int i = 0; i < color.length; i++) {
+ if (i > 0) {
+ str += ", ";
+ }
+ str += colorInt(color[i]);
+ }
+ return str + "]";
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder("\n");
+ int i = 0;
+ while (i < mPos) {
+ int cmd = mArray[i++];
+ int type = cmd & 0xFFFF;
+ switch (type) {
+
+ case TEXT_SIZE: {
+ ret.append(" TextSize(" + Float.intBitsToFloat(mArray[i++]));
+ }
+
+ break;
+ case TYPEFACE: {
+ int style = (cmd >> 16);
+ int weight = style & 0x3ff;
+ boolean italic = (style >> 10) > 0;
+ int font_type = mArray[i++];
+ ret.append(" TypeFace(" + (font_type + ", "
+ + weight + ", " + italic));
+ }
+ break;
+ case COLOR: {
+ ret.append(" Color(" + colorInt(mArray[i++]));
+ }
+ break;
+ case STROKE_WIDTH: {
+ ret.append(" StrokeWidth("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case STROKE_MITER: {
+ ret.append(" StrokeMiter("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case STROKE_CAP: {
+ ret.append(" StrokeCap("
+ + (cmd >> 16));
+ }
+ break;
+ case STYLE: {
+ ret.append(" Style(" + (cmd >> 16));
+ }
+ break;
+ case COLOR_FILTER: {
+ ret.append(" ColorFilter(color="
+ + colorInt(mArray[i++])
+ + ", mode=" + blendModeString(cmd >> 16));
+ }
+ break;
+ case SHADER: {
+ }
+ break;
+ case ALPHA: {
+ ret.append(" Alpha("
+ + (Float.intBitsToFloat(mArray[i++])));
+ }
+ break;
+ case IMAGE_FILTER_QUALITY: {
+ ret.append(" ImageFilterQuality(" + (cmd >> 16));
+ }
+ break;
+ case BLEND_MODE: {
+ ret.append(" BlendMode(" + blendModeString(cmd >> 16));
+ }
+ break;
+ case FILTER_BITMAP: {
+ ret.append(" FilterBitmap("
+ + (!((cmd >> 16) == 0)));
+ }
+ break;
+ case STROKE_JOIN: {
+ ret.append(" StrokeJoin(" + (cmd >> 16));
+ }
+ break;
+ case ANTI_ALIAS: {
+ ret.append(" AntiAlias(" + (cmd >> 16));
+ }
+ break;
+ case GRADIENT: {
+ i = callPrintGradient(cmd, mArray, i, ret);
+ }
+ }
+ ret.append("),\n");
+ }
+ return ret.toString();
+ }
+
+
+ int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) {
+ int ret = i;
+ int type = (cmd >> 16);
+ switch (type) {
+
+ case 0: {
+ p.append(" LinearGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" start = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ p.append(" end = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ int tileMode = array[ret++];
+ p.append(" tileMode = " + tileMode + "\n ");
+ }
+
+ break;
+ case 1: {
+ p.append(" RadialGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" center = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+ p.append(" radius =");
+ p.append(" " + Float.intBitsToFloat(array[ret++]) + ",\n");
+ int tileMode = array[ret++];
+ p.append(" tileMode = " + tileMode + "\n ");
+ }
+
+ break;
+ case 2: {
+ p.append(" SweepGradient(\n");
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < stops.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ p.append(" colors = " + colorInt(colors) + ",\n");
+ p.append(" stops = " + Arrays.toString(stops) + ",\n");
+ p.append(" center = ");
+ p.append("[" + Float.intBitsToFloat(array[ret++]));
+ p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n ");
+
+ }
+ break;
+ default: {
+ p.append("GRADIENT_??????!!!!");
+ }
+ }
+
+ return ret;
+ }
+
+ int callSetGradient(int cmd, int[] array, int i, PaintChanges p) {
+ int ret = i;
+ int gradientType = (cmd >> 16);
+
+ int len = array[ret++];
+ int[] colors = null;
+ if (len > 0) {
+ colors = new int[len];
+ for (int j = 0; j < colors.length; j++) {
+ colors[j] = array[ret++];
+ }
+ }
+ len = array[ret++];
+ float[] stops = null;
+ if (len > 0) {
+ stops = new float[len];
+ for (int j = 0; j < colors.length; j++) {
+ stops[j] = Float.intBitsToFloat(array[ret++]);
+ }
+ }
+
+ if (colors == null) {
+ return ret;
+ }
+
+
+ switch (gradientType) {
+
+ case LINEAR_GRADIENT: {
+ float startX = Float.intBitsToFloat(array[ret++]);
+ float startY = Float.intBitsToFloat(array[ret++]);
+ float endX = Float.intBitsToFloat(array[ret++]);
+ float endY = Float.intBitsToFloat(array[ret++]);
+ int tileMode = array[ret++];
+ p.setLinearGradient(colors, stops, startX,
+ startY, endX, endY, tileMode);
+ }
+
+ break;
+ case RADIAL_GRADIENT: {
+ float centerX = Float.intBitsToFloat(array[ret++]);
+ float centerY = Float.intBitsToFloat(array[ret++]);
+ float radius = Float.intBitsToFloat(array[ret++]);
+ int tileMode = array[ret++];
+ p.setRadialGradient(colors, stops, centerX, centerY,
+ radius, tileMode);
+ }
+ break;
+ case SWEEP_GRADIENT: {
+ float centerX = Float.intBitsToFloat(array[ret++]);
+ float centerY = Float.intBitsToFloat(array[ret++]);
+ p.setSweepGradient(colors, stops, centerX, centerY);
+ }
+ }
+
+ return ret;
+ }
+
+ public void writeBundle(WireBuffer buffer) {
+ buffer.writeInt(mPos);
+ for (int index = 0; index < mPos; index++) {
+ buffer.writeInt(mArray[index]);
+ }
+ }
+
+ public void readBundle(WireBuffer buffer) {
+ int len = buffer.readInt();
+ if (len <= 0 || len > 1024) {
+ throw new RuntimeException("buffer corrupt paint len = " + len);
+ }
+ mArray = new int[len];
+ for (int i = 0; i < mArray.length; i++) {
+ mArray[i] = buffer.readInt();
+ }
+ mPos = len;
+ }
+
+ public static final int TEXT_SIZE = 1; // float
+
+ public static final int COLOR = 4; // int
+ public static final int STROKE_WIDTH = 5; // float
+ public static final int STROKE_MITER = 6;
+ public static final int STROKE_CAP = 7; // int
+ public static final int STYLE = 8; // int
+ public static final int SHADER = 9; // int
+ public static final int IMAGE_FILTER_QUALITY = 10; // int
+ public static final int GRADIENT = 11;
+ public static final int ALPHA = 12;
+ public static final int COLOR_FILTER = 13;
+ public static final int ANTI_ALIAS = 14;
+ public static final int STROKE_JOIN = 15;
+ public static final int TYPEFACE = 16;
+ public static final int FILTER_BITMAP = 17;
+ public static final int BLEND_MODE = 18;
+
+
+ public static final int BLEND_MODE_CLEAR = 0;
+ public static final int BLEND_MODE_SRC = 1;
+ public static final int BLEND_MODE_DST = 2;
+ public static final int BLEND_MODE_SRC_OVER = 3;
+ public static final int BLEND_MODE_DST_OVER = 4;
+ public static final int BLEND_MODE_SRC_IN = 5;
+ public static final int BLEND_MODE_DST_IN = 6;
+ public static final int BLEND_MODE_SRC_OUT = 7;
+ public static final int BLEND_MODE_DST_OUT = 8;
+ public static final int BLEND_MODE_SRC_ATOP = 9;
+ public static final int BLEND_MODE_DST_ATOP = 10;
+ public static final int BLEND_MODE_XOR = 11;
+ public static final int BLEND_MODE_PLUS = 12;
+ public static final int BLEND_MODE_MODULATE = 13;
+ public static final int BLEND_MODE_SCREEN = 14;
+ public static final int BLEND_MODE_OVERLAY = 15;
+ public static final int BLEND_MODE_DARKEN = 16;
+ public static final int BLEND_MODE_LIGHTEN = 17;
+ public static final int BLEND_MODE_COLOR_DODGE = 18;
+ public static final int BLEND_MODE_COLOR_BURN = 19;
+ public static final int BLEND_MODE_HARD_LIGHT = 20;
+ public static final int BLEND_MODE_SOFT_LIGHT = 21;
+ public static final int BLEND_MODE_DIFFERENCE = 22;
+ public static final int BLEND_MODE_EXCLUSION = 23;
+ public static final int BLEND_MODE_MULTIPLY = 24;
+ public static final int BLEND_MODE_HUE = 25;
+ public static final int BLEND_MODE_SATURATION = 26;
+ public static final int BLEND_MODE_COLOR = 27;
+ public static final int BLEND_MODE_LUMINOSITY = 28;
+ public static final int BLEND_MODE_NULL = 29;
+ public static final int PORTER_MODE_ADD = 30;
+
+ public static final int FONT_NORMAL = 0;
+ public static final int FONT_BOLD = 1;
+ public static final int FONT_ITALIC = 2;
+ public static final int FONT_BOLD_ITALIC = 3;
+
+ public static final int FONT_TYPE_DEFAULT = 0;
+ public static final int FONT_TYPE_SANS_SERIF = 1;
+ public static final int FONT_TYPE_SERIF = 2;
+ public static final int FONT_TYPE_MONOSPACE = 3;
+
+ public static final int STYLE_FILL = 0;
+ public static final int STYLE_STROKE = 1;
+ public static final int STYLE_FILL_AND_STROKE = 2;
+ public static final int LINEAR_GRADIENT = 0;
+ public static final int RADIAL_GRADIENT = 1;
+ public static final int SWEEP_GRADIENT = 2;
+
+ /**
+ * sets a shader that draws a linear gradient along a line.
+ *
+ * @param startX The x-coordinate for the start of the gradient line
+ * @param startY The y-coordinate for the start of the gradient line
+ * @param endX The x-coordinate for the end of the gradient line
+ * @param endY The y-coordinate for the end of the gradient line
+ * @param colors The sRGB colors to be distributed along the gradient line
+ * @param stops May be null. The relative positions [0..1] of
+ * each corresponding color in the colors array. If this is null,
+ * the colors are distributed evenly along the gradient line.
+ * @param tileMode The Shader tiling mode
+ */
+ public void setLinearGradient(int[] colors,
+ float[] stops,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (LINEAR_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(startX);
+ mArray[mPos++] = Float.floatToRawIntBits(startY);
+ mArray[mPos++] = Float.floatToRawIntBits(endX);
+ mArray[mPos++] = Float.floatToRawIntBits(endY);
+ mArray[mPos++] = tileMode;
+ }
+
+ /**
+ * Set a shader that draws a sweep gradient around a center point.
+ *
+ * @param centerX The x-coordinate of the center
+ * @param centerY The y-coordinate of the center
+ * @param colors The sRGB colors to be distributed around the center.
+ * There must be at least 2 colors in the array.
+ * @param stops May be NULL. The relative position of
+ * each corresponding color in the colors array, beginning
+ * with 0 and ending with 1.0. If the values are not
+ * monotonic, the drawing may produce unexpected results.
+ * If positions is NULL, then the colors are automatically
+ * spaced evenly.
+ */
+ public void setSweepGradient(int[] colors, float[] stops, float centerX, float centerY) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (SWEEP_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(centerX);
+ mArray[mPos++] = Float.floatToRawIntBits(centerY);
+ }
+
+ /**
+ * Sets a shader that draws a radial gradient given the center and radius.
+ *
+ * @param centerX The x-coordinate of the center of the radius
+ * @param centerY The y-coordinate of the center of the radius
+ * @param radius Must be positive. The radius of the gradient.
+ * @param colors The sRGB colors distributed between the center and edge
+ * @param stops May be <code>null</code>.
+ * Valid values are between <code>0.0f</code> and
+ * <code>1.0f</code>. The relative position of each
+ * corresponding color in
+ * the colors array. If <code>null</code>, colors are
+ * distributed evenly
+ * between the center and edge of the circle.
+ * @param tileMode The Shader tiling mode
+ */
+ public void setRadialGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+ int startPos = mPos;
+ int len;
+ mArray[mPos++] = GRADIENT | (RADIAL_GRADIENT << 16);
+ mArray[mPos++] = len = (colors == null) ? 0 : colors.length;
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = colors[i];
+ }
+ mArray[mPos++] = len = (stops == null) ? 0 : stops.length;
+
+ for (int i = 0; i < len; i++) {
+ mArray[mPos++] = Float.floatToRawIntBits(stops[i]);
+ }
+ mArray[mPos++] = Float.floatToRawIntBits(centerX);
+ mArray[mPos++] = Float.floatToRawIntBits(centerY);
+ mArray[mPos++] = Float.floatToRawIntBits(radius);
+ mArray[mPos++] = tileMode;
+
+ }
+
+ /**
+ * Create a color filter that uses the specified color and Porter-Duff mode.
+ *
+ * @param color The ARGB source color used with the Porter-Duff mode
+ * @param mode The porter-duff mode that is applied
+ */
+ public void setColorFilter(int color, int mode) {
+ mArray[mPos] = COLOR_FILTER | (mode << 16);
+ mPos++;
+ mArray[mPos++] = color;
+ }
+
+ /**
+ * Set the paint's text size. This value must be > 0
+ *
+ * @param size set the paint's text size in pixel units.
+ */
+ public void setTextSize(float size) {
+ int p = mPos;
+ mArray[mPos] = TEXT_SIZE;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(size);
+ mPos++;
+ }
+
+ /**
+ * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace
+ * @param weight 100-1000
+ * @param italic tur
+ */
+ public void setTextStyle(int fontType, int weight, boolean italic) {
+ int style = (weight & 0x3FF) | (italic ? 2048 : 0); // pack the weight and italic
+ mArray[mPos++] = TYPEFACE | (style << 16);
+ mArray[mPos++] = fontType;
+ }
+
+ /**
+ * Set the width for stroking.
+ * Pass 0 to stroke in hairline mode.
+ * Hairlines always draws a single pixel independent of the canvas's matrix.
+ *
+ * @param width set the paint's stroke width, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeWidth(float width) {
+ mArray[mPos] = STROKE_WIDTH;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(width);
+ mPos++;
+ }
+
+ public void setColor(int color) {
+ mArray[mPos] = COLOR;
+ mPos++;
+ mArray[mPos] = color;
+ mPos++;
+ }
+
+ /**
+ * Set the paint's Cap.
+ *
+ * @param cap set the paint's line cap style, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeCap(int cap) {
+ mArray[mPos] = STROKE_CAP | (cap << 16);
+ mPos++;
+ }
+
+ public void setStyle(int style) {
+ mArray[mPos] = STYLE | (style << 16);
+ mPos++;
+ }
+
+ public void setShader(int shader, String shaderString) {
+ mArray[mPos] = SHADER | (shader << 16);
+ mPos++;
+ }
+
+ public void setAlpha(float alpha) {
+ mArray[mPos] = ALPHA;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(alpha);
+ mPos++;
+ }
+
+ /**
+ * Set the paint's stroke miter value. This is used to control the behavior
+ * of miter joins when the joins angle is sharp. This value must be >= 0.
+ *
+ * @param miter set the miter limit on the paint, used whenever the paint's
+ * style is Stroke or StrokeAndFill.
+ */
+ public void setStrokeMiter(float miter) {
+ mArray[mPos] = STROKE_MITER;
+ mPos++;
+ mArray[mPos] = Float.floatToRawIntBits(miter);
+ mPos++;
+ }
+
+ /**
+ * Set the paint's Join.
+ *
+ * @param join set the paint's Join, used whenever the paint's style is
+ * Stroke or StrokeAndFill.
+ */
+ public void setStrokeJoin(int join) {
+ mArray[mPos] = STROKE_JOIN | (join << 16);
+ mPos++;
+ }
+
+ public void setFilterBitmap(boolean filter) {
+ mArray[mPos] = FILTER_BITMAP | (filter ? (1 << 16) : 0);
+ mPos++;
+ }
+
+ /**
+ * Set or clear the blend mode. A blend mode defines how source pixels
+ * (generated by a drawing command) are composited with the
+ * destination pixels
+ * (content of the render target).
+ *
+ *
+ * @param blendmode The blend mode to be installed in the paint
+ */
+ public void setBlendMode(int blendmode) {
+ mArray[mPos] = BLEND_MODE | (blendmode << 16);
+ mPos++;
+ }
+
+ /**
+ * Helper for setFlags(), setting or clearing the ANTI_ALIAS_FLAG bit
+ * AntiAliasing smooths out the edges of what is being drawn, but is has
+ * no impact on the interior of the shape. See setDither() and
+ * setFilterBitmap() to affect how colors are treated.
+ *
+ * @param aa true to set the antialias bit in the flags, false to clear it
+ */
+ public void setAntiAlias(boolean aa) {
+ mArray[mPos] = ANTI_ALIAS | (((aa) ? 1 : 0) << 16);
+ mPos++;
+ }
+
+ public void clear(long mask) { // unused for now
+ }
+
+ public void reset() {
+ mPos = 0;
+ }
+
+ public static String blendModeString(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return "CLEAR";
+ case PaintBundle.BLEND_MODE_SRC:
+ return "SRC";
+ case PaintBundle.BLEND_MODE_DST:
+ return "DST";
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return "SRC_OVER";
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return "DST_OVER";
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return "SRC_IN";
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return "DST_IN";
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return "SRC_OUT";
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return "DST_OUT";
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return "SRC_ATOP";
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return "DST_ATOP";
+ case PaintBundle.BLEND_MODE_XOR:
+ return "XOR";
+ case PaintBundle.BLEND_MODE_PLUS:
+ return "PLUS";
+ case PaintBundle.BLEND_MODE_MODULATE:
+ return "MODULATE";
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return "SCREEN";
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return "OVERLAY";
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return "DARKEN";
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return "LIGHTEN";
+ case PaintBundle.BLEND_MODE_COLOR_DODGE:
+ return "COLOR_DODGE";
+ case PaintBundle.BLEND_MODE_COLOR_BURN:
+ return "COLOR_BURN";
+ case PaintBundle.BLEND_MODE_HARD_LIGHT:
+ return "HARD_LIGHT";
+ case PaintBundle.BLEND_MODE_SOFT_LIGHT:
+ return "SOFT_LIGHT";
+ case PaintBundle.BLEND_MODE_DIFFERENCE:
+ return "DIFFERENCE";
+ case PaintBundle.BLEND_MODE_EXCLUSION:
+ return "EXCLUSION";
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return "MULTIPLY";
+ case PaintBundle.BLEND_MODE_HUE:
+ return "HUE";
+ case PaintBundle.BLEND_MODE_SATURATION:
+ return "SATURATION";
+ case PaintBundle.BLEND_MODE_COLOR:
+ return "COLOR";
+ case PaintBundle.BLEND_MODE_LUMINOSITY:
+ return "LUMINOSITY";
+ case PaintBundle.BLEND_MODE_NULL:
+ return "null";
+ case PaintBundle.PORTER_MODE_ADD:
+ return "ADD";
+ }
+ return "null";
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
new file mode 100644
index 0000000..994bf6d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+public class PaintChangeAdapter implements PaintChanges {
+
+ @Override
+ public void setTextSize(float size) {
+
+ }
+
+ @Override
+ public void setTypeFace(int fontType, int weight, boolean italic) {
+
+ }
+
+
+ @Override
+ public void setStrokeWidth(float width) {
+
+ }
+
+ @Override
+ public void setColor(int color) {
+
+ }
+
+ @Override
+ public void setStrokeCap(int cap) {
+
+ }
+
+ @Override
+ public void setStyle(int style) {
+
+ }
+
+ @Override
+ public void setShader(int shader, String shaderString) {
+
+ }
+
+ @Override
+ public void setImageFilterQuality(int quality) {
+
+ }
+
+ @Override
+ public void setAlpha(float a) {
+
+ }
+
+ @Override
+ public void setStrokeMiter(float miter) {
+
+ }
+
+ @Override
+ public void setStrokeJoin(int join) {
+
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+
+ }
+
+ @Override
+ public void setBlendMode(int blendmode) {
+
+ }
+
+ @Override
+ public void setAntiAlias(boolean aa) {
+
+ }
+
+ @Override
+ public void clear(long mask) {
+
+ }
+
+ @Override
+ public void setLinearGradient(int[] colorsArray,
+ float[] stopsArray,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+
+ }
+
+ @Override
+ public void setRadialGradient(int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+
+ }
+
+ @Override
+ public void setSweepGradient(int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY) {
+
+ }
+
+ @Override
+ public void setColorFilter(int color, int mode) {
+
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
new file mode 100644
index 0000000..87e58ac
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+public interface PaintChanges {
+
+
+ int CLEAR_TEXT_STYLE = 1 << (PaintBundle.TYPEFACE - 1);
+ int CLEAR_COLOR = 1 << (PaintBundle.COLOR - 1);
+ int CLEAR_STROKE_WIDTH = 1 << (PaintBundle.STROKE_WIDTH - 1);
+ int CLEAR_STROKE_MITER = 1 << (PaintBundle.STROKE_MITER - 1);
+ int CLEAR_CAP = 1 << (PaintBundle.STROKE_CAP - 1);
+ int CLEAR_STYLE = 1 << (PaintBundle.STYLE - 1);
+ int CLEAR_SHADER = 1 << (PaintBundle.SHADER - 1);
+ int CLEAR_IMAGE_FILTER_QUALITY =
+ 1 << (PaintBundle.IMAGE_FILTER_QUALITY - 1);
+ int CLEAR_RADIENT = 1 << (PaintBundle.GRADIENT - 1);
+ int CLEAR_ALPHA = 1 << (PaintBundle.ALPHA - 1);
+ int CLEAR_COLOR_FILTER = 1 << (PaintBundle.COLOR_FILTER - 1);
+ int VALID_BITS = 0x1FFF; // only the first 13 bit are valid now
+
+
+ void setTextSize(float size);
+ void setStrokeWidth(float width);
+ void setColor(int color);
+ void setStrokeCap(int cap);
+ void setStyle(int style);
+ void setShader(int shader, String shaderString);
+ void setImageFilterQuality(int quality);
+ void setAlpha(float a);
+ void setStrokeMiter(float miter);
+ void setStrokeJoin(int join);
+ void setFilterBitmap(boolean filter);
+ void setBlendMode(int mode);
+ void setAntiAlias(boolean aa);
+ void clear(long mask);
+ void setLinearGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode
+ );
+
+ void setRadialGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode
+ );
+
+ void setSweepGradient(
+ int[] colorsArray,
+ float[] stopsArray,
+ float centerX,
+ float centerY
+ );
+
+
+ void setColorFilter(int color, int mode);
+
+ void setTypeFace(int fontType, int weight, boolean italic);
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
new file mode 100644
index 0000000..1c0bec7
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.paint;
+
+public interface TextPaint {
+ void setARGB(int a, int r, int g, int b);
+
+ void setDither(boolean dither);
+
+ void setElegantTextHeight(boolean elegant);
+
+ void setEndHyphenEdit(int endHyphen);
+
+ void setFakeBoldText(boolean fakeBoldText);
+
+ void setFlags(int flags);
+
+ void setFontFeatureSettings(String settings);
+
+ void setHinting(int mode);
+
+ void setLetterSpacing(float letterSpacing);
+
+ void setLinearText(boolean linearText);
+
+ void setShadowLayer(float radius, float dx, float dy, int shadowColor);
+
+ void setStartHyphenEdit(int startHyphen);
+
+ void setStrikeThruText(boolean strikeThruText);
+
+ void setStrokeCap(int cap);
+
+ void setSubpixelText(boolean subpixelText);
+
+ void setTextAlign(int align);
+
+ void setTextLocale(int locale);
+
+ void setTextLocales(int localesArray);
+
+ void setTextScaleX(float scaleX);
+
+ void setTextSize(float textSize);
+
+ void setTextSkewX(float skewX);
+
+ void setUnderlineText(boolean underlineText);
+
+ void setWordSpacing(float wordSpacing);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index 3799cf6..d0d6e69 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -16,12 +16,25 @@
package com.android.internal.widget.remotecompose.player.platform;
import android.graphics.Bitmap;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.LinearGradient;
import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.RadialGradient;
import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.graphics.Typeface;
import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges;
/**
* An implementation of PaintContext for the Android Canvas.
@@ -71,7 +84,8 @@
int cdId) {
AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
if (androidContext.mRemoteComposeState.containsId(imageId)) {
- Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState.getFromId(imageId);
+ Bitmap bitmap = (Bitmap) androidContext.mRemoteComposeState
+ .getFromId(imageId);
mCanvas.drawBitmap(
bitmap,
new Rect(srcLeft, srcTop, srcRight, srcBottom),
@@ -89,5 +103,501 @@
public void translate(float translateX, float translateY) {
mCanvas.translate(translateX, translateY);
}
+
+ @Override
+ public void drawArc(float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ mCanvas.drawArc(left, top, right, bottom, startAngle,
+ sweepAngle, true, mPaint);
+ }
+
+ @Override
+ public void drawBitmap(int id,
+ float left,
+ float top,
+ float right,
+ float bottom) {
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ if (androidContext.mRemoteComposeState.containsId(id)) {
+ Bitmap bitmap =
+ (Bitmap) androidContext.mRemoteComposeState.getFromId(id);
+ Rect src = new Rect(0, 0,
+ bitmap.getWidth(), bitmap.getHeight());
+ RectF dst = new RectF(left, top, right, bottom);
+ mCanvas.drawBitmap(bitmap, src, dst, mPaint);
+ }
+ }
+
+ @Override
+ public void drawCircle(float centerX, float centerY, float radius) {
+ mCanvas.drawCircle(centerX, centerY, radius, mPaint);
+ }
+
+ @Override
+ public void drawLine(float x1, float y1, float x2, float y2) {
+ mCanvas.drawLine(x1, y1, x2, y2, mPaint);
+ }
+
+ @Override
+ public void drawOval(float left, float top, float right, float bottom) {
+ mCanvas.drawOval(left, top, right, bottom, mPaint);
+ }
+
+ @Override
+ public void drawPath(int id, float start, float end) {
+ mCanvas.drawPath(getPath(id, start, end), mPaint);
+ }
+
+ @Override
+ public void drawRect(float left, float top, float right, float bottom) {
+ mCanvas.drawRect(left, top, right, bottom, mPaint);
+ }
+
+ @Override
+ public void drawRoundRect(float left,
+ float top,
+ float right,
+ float bottom,
+ float radiusX,
+ float radiusY) {
+ mCanvas.drawRoundRect(left, top, right, bottom,
+ radiusX, radiusY, mPaint);
+ }
+
+ @Override
+ public void drawTextOnPath(int textId,
+ int pathId,
+ float hOffset,
+ float vOffset) {
+ mCanvas.drawTextOnPath(getText(textId), getPath(pathId, 0, 1), hOffset, vOffset, mPaint);
+ }
+
+ @Override
+ public void drawTextRun(int textID,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean rtl) {
+ String textToPaint = getText(textID).substring(start, end);
+ mCanvas.drawText(textToPaint, x, y, mPaint);
+ }
+
+ @Override
+ public void drawTweenPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float end) {
+ mCanvas.drawPath(getPath(path1Id, path2Id, tween, start, end), mPaint);
+ }
+
+ private static PorterDuff.Mode origamiToPorterDuffMode(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return PorterDuff.Mode.CLEAR;
+ case PaintBundle.BLEND_MODE_SRC:
+ return PorterDuff.Mode.SRC;
+ case PaintBundle.BLEND_MODE_DST:
+ return PorterDuff.Mode.DST;
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return PorterDuff.Mode.SRC_OVER;
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return PorterDuff.Mode.DST_OVER;
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return PorterDuff.Mode.SRC_IN;
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return PorterDuff.Mode.DST_IN;
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return PorterDuff.Mode.SRC_OUT;
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return PorterDuff.Mode.DST_OUT;
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return PorterDuff.Mode.SRC_ATOP;
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return PorterDuff.Mode.DST_ATOP;
+ case PaintBundle.BLEND_MODE_XOR:
+ return PorterDuff.Mode.XOR;
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return PorterDuff.Mode.SCREEN;
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return PorterDuff.Mode.OVERLAY;
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return PorterDuff.Mode.DARKEN;
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return PorterDuff.Mode.LIGHTEN;
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return PorterDuff.Mode.MULTIPLY;
+ case PaintBundle.PORTER_MODE_ADD:
+ return PorterDuff.Mode.ADD;
+ }
+ return PorterDuff.Mode.SRC_OVER;
+ }
+
+ public static BlendMode origamiToBlendMode(int mode) {
+ switch (mode) {
+ case PaintBundle.BLEND_MODE_CLEAR:
+ return BlendMode.CLEAR;
+ case PaintBundle.BLEND_MODE_SRC:
+ return BlendMode.SRC;
+ case PaintBundle.BLEND_MODE_DST:
+ return BlendMode.DST;
+ case PaintBundle.BLEND_MODE_SRC_OVER:
+ return BlendMode.SRC_OVER;
+ case PaintBundle.BLEND_MODE_DST_OVER:
+ return BlendMode.DST_OVER;
+ case PaintBundle.BLEND_MODE_SRC_IN:
+ return BlendMode.SRC_IN;
+ case PaintBundle.BLEND_MODE_DST_IN:
+ return BlendMode.DST_IN;
+ case PaintBundle.BLEND_MODE_SRC_OUT:
+ return BlendMode.SRC_OUT;
+ case PaintBundle.BLEND_MODE_DST_OUT:
+ return BlendMode.DST_OUT;
+ case PaintBundle.BLEND_MODE_SRC_ATOP:
+ return BlendMode.SRC_ATOP;
+ case PaintBundle.BLEND_MODE_DST_ATOP:
+ return BlendMode.DST_ATOP;
+ case PaintBundle.BLEND_MODE_XOR:
+ return BlendMode.XOR;
+ case PaintBundle.BLEND_MODE_PLUS:
+ return BlendMode.PLUS;
+ case PaintBundle.BLEND_MODE_MODULATE:
+ return BlendMode.MODULATE;
+ case PaintBundle.BLEND_MODE_SCREEN:
+ return BlendMode.SCREEN;
+ case PaintBundle.BLEND_MODE_OVERLAY:
+ return BlendMode.OVERLAY;
+ case PaintBundle.BLEND_MODE_DARKEN:
+ return BlendMode.DARKEN;
+ case PaintBundle.BLEND_MODE_LIGHTEN:
+ return BlendMode.LIGHTEN;
+ case PaintBundle.BLEND_MODE_COLOR_DODGE:
+ return BlendMode.COLOR_DODGE;
+ case PaintBundle.BLEND_MODE_COLOR_BURN:
+ return BlendMode.COLOR_BURN;
+ case PaintBundle.BLEND_MODE_HARD_LIGHT:
+ return BlendMode.HARD_LIGHT;
+ case PaintBundle.BLEND_MODE_SOFT_LIGHT:
+ return BlendMode.SOFT_LIGHT;
+ case PaintBundle.BLEND_MODE_DIFFERENCE:
+ return BlendMode.DIFFERENCE;
+ case PaintBundle.BLEND_MODE_EXCLUSION:
+ return BlendMode.EXCLUSION;
+ case PaintBundle.BLEND_MODE_MULTIPLY:
+ return BlendMode.MULTIPLY;
+ case PaintBundle.BLEND_MODE_HUE:
+ return BlendMode.HUE;
+ case PaintBundle.BLEND_MODE_SATURATION:
+ return BlendMode.SATURATION;
+ case PaintBundle.BLEND_MODE_COLOR:
+ return BlendMode.COLOR;
+ case PaintBundle.BLEND_MODE_LUMINOSITY:
+ return BlendMode.LUMINOSITY;
+ case PaintBundle.BLEND_MODE_NULL:
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public void applyPaint(PaintBundle mPaintData) {
+ mPaintData.applyPaintChange(new PaintChanges() {
+ @Override
+ public void setTextSize(float size) {
+ mPaint.setTextSize(size);
+ }
+
+ @Override
+ public void setTypeFace(int fontType, int weight, boolean italic) {
+ int[] type = new int[]{Typeface.NORMAL, Typeface.BOLD,
+ Typeface.ITALIC, Typeface.BOLD_ITALIC};
+
+ switch (fontType) {
+ case PaintBundle.FONT_TYPE_DEFAULT: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.DEFAULT);
+ } else {
+ mPaint.setTypeface(Typeface.create(Typeface.DEFAULT,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_SERIF: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.SERIF);
+ } else {
+ mPaint.setTypeface(Typeface.create(Typeface.SERIF,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_SANS_SERIF: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.SANS_SERIF);
+ } else {
+ mPaint.setTypeface(
+ Typeface.create(Typeface.SANS_SERIF,
+ weight, italic));
+ }
+ break;
+ }
+ case PaintBundle.FONT_TYPE_MONOSPACE: {
+ if (weight == 400 && !italic) { // for normal case
+ mPaint.setTypeface(Typeface.MONOSPACE);
+ } else {
+ mPaint.setTypeface(
+ Typeface.create(Typeface.MONOSPACE,
+ weight, italic));
+ }
+
+ break;
+ }
+ }
+
+
+ }
+
+
+ @Override
+ public void setStrokeWidth(float width) {
+ mPaint.setStrokeWidth(width);
+ }
+
+ @Override
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ }
+
+ @Override
+ public void setStrokeCap(int cap) {
+ mPaint.setStrokeCap(Paint.Cap.values()[cap]);
+ }
+
+ @Override
+ public void setStyle(int style) {
+ mPaint.setStyle(Paint.Style.values()[style]);
+ }
+
+ @Override
+ public void setShader(int shader, String shaderString) {
+
+ }
+
+ @Override
+ public void setImageFilterQuality(int quality) {
+ System.out.println(">>>>>>>>>>>> ");
+ }
+
+ @Override
+ public void setBlendMode(int mode) {
+ mPaint.setBlendMode(origamiToBlendMode(mode));
+ }
+
+ @Override
+ public void setAlpha(float a) {
+ mPaint.setAlpha((int) (255 * a));
+ }
+
+ @Override
+ public void setStrokeMiter(float miter) {
+ mPaint.setStrokeMiter(miter);
+ }
+
+ @Override
+ public void setStrokeJoin(int join) {
+ mPaint.setStrokeJoin(Paint.Join.values()[join]);
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ mPaint.setFilterBitmap(filter);
+ }
+
+
+ @Override
+ public void setAntiAlias(boolean aa) {
+ mPaint.setAntiAlias(aa);
+ }
+
+ @Override
+ public void clear(long mask) {
+ if (true) return;
+ long m = mask;
+ int k = 1;
+ while (m > 0) {
+ if ((m & 1) == 1L) {
+ switch (k) {
+
+ case PaintBundle.COLOR_FILTER:
+ mPaint.setColorFilter(null);
+ System.out.println(">>>>>>>>>>>>> CLEAR!!!!");
+ break;
+ }
+ }
+ k++;
+ m = m >> 1;
+ }
+ }
+
+ Shader.TileMode[] mTilesModes = new Shader.TileMode[]{
+ Shader.TileMode.CLAMP,
+ Shader.TileMode.REPEAT,
+ Shader.TileMode.MIRROR};
+
+
+ @Override
+ public void setLinearGradient(int[] colors,
+ float[] stops,
+ float startX,
+ float startY,
+ float endX,
+ float endY,
+ int tileMode) {
+ mPaint.setShader(new LinearGradient(startX,
+ startY,
+ endX,
+ endY, colors, stops, mTilesModes[tileMode]));
+
+ }
+
+ @Override
+ public void setRadialGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY,
+ float radius,
+ int tileMode) {
+ mPaint.setShader(new RadialGradient(centerX, centerY, radius,
+ colors, stops, mTilesModes[tileMode]));
+ }
+
+ @Override
+ public void setSweepGradient(int[] colors,
+ float[] stops,
+ float centerX,
+ float centerY) {
+ mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops));
+
+ }
+
+ @Override
+ public void setColorFilter(int color, int mode) {
+ PorterDuff.Mode pmode = origamiToPorterDuffMode(mode);
+ System.out.println("setting color filter to " + pmode.name());
+ if (pmode != null) {
+ mPaint.setColorFilter(
+ new PorterDuffColorFilter(color, pmode));
+ }
+ }
+ });
+ }
+
+ @Override
+ public void mtrixScale(float scaleX,
+ float scaleY,
+ float centerX,
+ float centerY) {
+ if (Float.isNaN(centerX)) {
+ mCanvas.scale(scaleX, scaleY);
+ } else {
+ mCanvas.scale(scaleX, scaleY, centerX, centerY);
+ }
+ }
+
+ @Override
+ public void matrixTranslate(float translateX, float translateY) {
+ mCanvas.translate(translateX, translateY);
+ }
+
+ @Override
+ public void matrixSkew(float skewX, float skewY) {
+ mCanvas.skew(skewX, skewY);
+ }
+
+ @Override
+ public void matrixRotate(float rotate, float pivotX, float pivotY) {
+ if (Float.isNaN(pivotX)) {
+ mCanvas.rotate(rotate);
+ } else {
+ mCanvas.rotate(rotate, pivotX, pivotY);
+
+ }
+ }
+
+ @Override
+ public void matrixSave() {
+ mCanvas.save();
+ }
+
+ @Override
+ public void matrixRestore() {
+ mCanvas.restore();
+ }
+
+ @Override
+ public void clipRect(float left, float top, float right, float bottom) {
+ mCanvas.clipRect(left, top, right, bottom);
+ }
+
+ @Override
+ public void clipPath(int pathId, int regionOp) {
+ Path path = getPath(pathId, 0, 1);
+ if (regionOp == ClipPath.DIFFERENCE) {
+ mCanvas.clipOutPath(path); // DIFFERENCE
+ } else {
+ mCanvas.clipPath(path); // INTERSECT
+ }
+ }
+
+ private Path getPath(int path1Id,
+ int path2Id,
+ float tween,
+ float start,
+ float end) {
+ if (tween == 0.0f) {
+ return getPath(path1Id, start, end);
+ }
+ if (tween == 1.0f) {
+ return getPath(path2Id, start, end);
+ }
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ float[] data1 =
+ (float[]) androidContext.mRemoteComposeState.getFromId(path1Id);
+ float[] data2 =
+ (float[]) androidContext.mRemoteComposeState.getFromId(path2Id);
+ float[] tmp = new float[data2.length];
+ for (int i = 0; i < tmp.length; i++) {
+ if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) {
+ tmp[i] = data1[i];
+ } else {
+ tmp[i] = (data2[i] - data1[i]) * tween + data1[i];
+ }
+ }
+ Path path = new Path();
+ FloatsToPath.genPath(path, tmp, start, end);
+ return path;
+ }
+
+ private Path getPath(int id, float start, float end) {
+ AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+ Path path = new Path();
+ if (androidContext.mRemoteComposeState.containsId(id)) {
+ float[] data =
+ (float[]) androidContext.mRemoteComposeState.getFromId(id);
+ FloatsToPath.genPath(path, data, start, end);
+ }
+ return path;
+ }
+
+ private String getText(int id) {
+ return (String) mContext.mRemoteComposeState.getFromId(id);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index ce15855..270e96f 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -43,6 +43,13 @@
// Data handling
///////////////////////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void loadPathData(int instanceId, float[] floatPath) {
+ if (!mRemoteComposeState.containsId(instanceId)) {
+ mRemoteComposeState.cache(instanceId, floatPath);
+ }
+ }
+
/**
* Decode a byte array into an image and cache it using the given imageId
*
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
new file mode 100644
index 0000000..2d766f8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.player.platform;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.idFromNan;
+
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.os.Build;
+
+import com.android.internal.widget.remotecompose.core.operations.PathData;
+
+public class FloatsToPath {
+ public static void genPath(Path retPath,
+ float[] floatPath,
+ float start,
+ float stop) {
+ int i = 0;
+ Path path = new Path(); // todo this should be cached for performance
+ while (i < floatPath.length) {
+ switch (idFromNan(floatPath[i])) {
+ case PathData.MOVE: {
+ i++;
+ path.moveTo(floatPath[i + 0], floatPath[i + 1]);
+ i += 2;
+ }
+ break;
+ case PathData.LINE: {
+ i += 3;
+ path.lineTo(floatPath[i + 0], floatPath[i + 1]);
+ i += 2;
+ }
+ break;
+ case PathData.QUADRATIC: {
+ i += 3;
+ path.quadTo(
+ floatPath[i + 0],
+ floatPath[i + 1],
+ floatPath[i + 2],
+ floatPath[i + 3]
+ );
+ i += 4;
+
+ }
+ break;
+ case PathData.CONIC: {
+ i += 3;
+ if (Build.VERSION.SDK_INT >= 34) {
+ path.conicTo(
+ floatPath[i + 0], floatPath[i + 1],
+ floatPath[i + 2], floatPath[i + 3],
+ floatPath[i + 4]
+ );
+ }
+ i += 5;
+ }
+ break;
+ case PathData.CUBIC: {
+ i += 3;
+ path.cubicTo(
+ floatPath[i + 0], floatPath[i + 1],
+ floatPath[i + 2], floatPath[i + 3],
+ floatPath[i + 4], floatPath[i + 5]
+ );
+ i += 6;
+ }
+ break;
+ case PathData.CLOSE: {
+
+ path.close();
+ i++;
+ }
+ break;
+ case PathData.DONE: {
+ i++;
+ }
+ break;
+ default: {
+ System.err.println(" Odd command "
+ + idFromNan(floatPath[i]));
+ }
+ }
+ }
+
+ retPath.reset();
+ if (start > 0f || stop < 1f) {
+ if (start < stop) {
+
+ PathMeasure measure = new PathMeasure(); // todo cached
+ measure.setPath(path, false);
+ float len = measure.getLength();
+ float scaleStart = Math.max(start, 0f) * len;
+ float scaleStop = Math.min(stop, 1f) * len;
+ measure.getSegment(scaleStart, scaleStop, retPath,
+ true);
+ }
+ } else {
+
+ retPath.addPath(path);
+ }
+ }
+}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 5f3a1b5..72b98a2 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -523,21 +523,23 @@
}
}
-static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz)
-{
- return Camera::getNumberOfCameras();
+static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz, jint deviceId,
+ jint devicePolicy) {
+ return Camera::getNumberOfCameras(deviceId, devicePolicy);
}
static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
- jboolean overrideToPortrait, jobject info_obj) {
+ jboolean overrideToPortrait, jint deviceId,
+ jint devicePolicy, jobject info_obj) {
CameraInfo cameraInfo;
- if (cameraId >= Camera::getNumberOfCameras() || cameraId < 0) {
+ if (cameraId >= Camera::getNumberOfCameras(deviceId, devicePolicy) || cameraId < 0) {
ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId);
jniThrowRuntimeException(env, "Unknown camera ID");
return;
}
- status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
+ status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+ &cameraInfo);
if (rc != NO_ERROR) {
jniThrowRuntimeException(env, "Fail to get camera info");
return;
@@ -556,7 +558,8 @@
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint cameraId, jstring clientPackageName,
jboolean overrideToPortrait,
- jboolean forceSlowJpegMode) {
+ jboolean forceSlowJpegMode, jint deviceId,
+ jint devicePolicy) {
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
env->GetStringChars(clientPackageName, NULL));
@@ -568,7 +571,8 @@
int targetSdkVersion = android_get_application_target_sdk_version();
sp<Camera> camera =
Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
- targetSdkVersion, overrideToPortrait, forceSlowJpegMode);
+ targetSdkVersion, overrideToPortrait, forceSlowJpegMode, deviceId,
+ devicePolicy);
if (camera == NULL) {
return -EACCES;
}
@@ -596,7 +600,8 @@
// Update default display orientation in case the sensor is reverse-landscape
CameraInfo cameraInfo;
- status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
+ status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, deviceId, devicePolicy,
+ &cameraInfo);
if (rc != NO_ERROR) {
ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
return rc;
@@ -1051,10 +1056,10 @@
//-------------------------------------------------
static const JNINativeMethod camMethods[] = {
- {"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
- {"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
+ {"_getNumberOfCameras", "(II)I", (void *)android_hardware_Camera_getNumberOfCameras},
+ {"_getCameraInfo", "(IZIILandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZ)I",
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;ZZII)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index d2d5186..2068bd7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -88,6 +88,7 @@
jclass mClass;
jmethodID mForceGc;
jmethodID mProxyLimitCallback;
+ jmethodID mProxyWarningCallback;
} gBinderInternalOffsets;
@@ -1240,7 +1241,7 @@
gCollectedAtRefs = gNumLocalRefsCreated + gNumDeathRefsCreated;
}
-static void android_os_BinderInternal_proxyLimitcallback(int uid)
+static void android_os_BinderInternal_proxyLimitCallback(int uid)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
@@ -1254,6 +1255,20 @@
}
}
+static void android_os_BinderInternal_proxyWarningCallback(int uid)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+ gBinderInternalOffsets.mProxyWarningCallback,
+ uid);
+
+ if (env->ExceptionCheck()) {
+ ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
+ binder_report_exception(env, excep.get(),
+ "*** Uncaught exception in binderProxyWarningCallbackFromNative");
+ }
+}
+
static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz,
jboolean enable)
{
@@ -1278,9 +1293,10 @@
}
static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz,
- jint high, jint low)
+ jint high, jint low,
+ jint warning)
{
- BpBinder::setBinderProxyCountWatermarks(high, low);
+ BpBinder::setBinderProxyCountWatermarks(high, low, warning);
}
// ----------------------------------------------------------------------------
@@ -1295,7 +1311,7 @@
{ "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled },
{ "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts },
{ "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount },
- { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
+ { "nSetBinderProxyCountWatermarks", "(III)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
};
const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
@@ -1307,6 +1323,8 @@
gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V");
gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V");
+ gBinderInternalOffsets.mProxyWarningCallback =
+ GetStaticMethodIDOrDie(env, clazz, "binderProxyWarningCallbackFromNative", "(I)V");
jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray");
gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass);
@@ -1315,7 +1333,8 @@
gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put",
"(II)V");
- BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback);
+ BpBinder::setBinderProxyCountEventCallback(android_os_BinderInternal_proxyLimitCallback,
+ android_os_BinderInternal_proxyWarningCallback);
return RegisterMethodsOrDie(
env, kBinderInternalPathName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6431db9..cfe6f73 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7986,6 +7986,7 @@
content URI trigger and network constraint set.
<p>This is a special access permission that can be revoked by the system or the user.
<p>Protection level: signature|privileged|appop
+ @hide
-->
<permission android:name="android.permission.RUN_BACKUP_JOBS"
android:protectionLevel="signature|privileged|appop"/>
diff --git a/core/res/res/drawable/ic_bt_hearing_aid.xml b/core/res/res/drawable/ic_bt_hearing_aid.xml
index e14c99b..1517147 100644
--- a/core/res/res/drawable/ic_bt_hearing_aid.xml
+++ b/core/res/res/drawable/ic_bt_hearing_aid.xml
@@ -14,10 +14,11 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a3dba48..c6706cb 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5312,6 +5312,13 @@
<!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
<string name="zen_mode_implicit_deactivated">Off</string>
+ <!-- [CHAR LIMIT=40] General divider text when concatenating multiple items in a text summary, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_summary_divider_text">,\u0020</string>
+ <!-- [CHAR LIMIT=40] General template for a symbolic start - end range in a text summary, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_summary_range_symbol_combination"><xliff:g id="start" example="Sun">%1$s</xliff:g> - <xliff:g id="end" example="Thu">%2$s</xliff:g></string>
+ <!-- [CHAR LIMIT=40] Event-based rule calendar option value for any calendar, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_event_calendar_any">Any calendar</string>
+
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by"><xliff:g id="third_party">%1$s</xliff:g> is muting some sounds</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06c0188..fe4e4f04 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2609,6 +2609,10 @@
<java-symbol type="string" name="zen_mode_implicit_trigger_description" />
<java-symbol type="string" name="zen_mode_implicit_activated" />
<java-symbol type="string" name="zen_mode_implicit_deactivated" />
+ <java-symbol type="string" name="zen_mode_trigger_summary_divider_text" />
+ <java-symbol type="string" name="zen_mode_trigger_summary_range_symbol_combination" />
+ <java-symbol type="string" name="zen_mode_trigger_event_calendar_any" />
+
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
<java-symbol type="array" name="config_system_condition_providers" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 24031cad..4808204 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -134,6 +134,8 @@
":BinderDeathRecipientHelperApp1",
":BinderDeathRecipientHelperApp2",
":com.android.cts.helpers.aosp",
+ ":BinderProxyCountingTestApp",
+ ":BinderProxyCountingTestService",
],
}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 62d58b6..c05ea3d 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -254,6 +254,17 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.AbsListViewActivity"
+ android:label="AbsListViewActivity"
+ android:screenOrientation="portrait"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.DatePickerActivity"
android:label="DatePickerActivity"
android:screenOrientation="portrait"
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 05b309b..bf2a5b8 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -22,6 +22,8 @@
<option name="test-file-name" value="FrameworksCoreTests.apk" />
<option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
<option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
+ <option name="test-file-name" value="BinderProxyCountingTestApp.apk" />
+ <option name="test-file-name" value="BinderProxyCountingTestService.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
index a971730..c8407b8 100644
--- a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
@@ -16,6 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.binderproxycountingtestapp">
+ <queries>
+ <package android:name="com.android.frameworks.coretests.binderproxycountingtestservice" />
+ </queries>
<application>
<service android:name=".BpcTestAppCmdService"
android:exported="true"/>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
index 5aae1203..a7e97d3 100644
--- a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
+++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
@@ -17,14 +17,15 @@
package com.android.frameworks.coretests.binderproxycountingtestapp;
import android.app.Service;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.database.ContentObserver;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.Log;
import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
@@ -49,24 +50,20 @@
private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() {
- private ArrayList<BroadcastReceiver> mBrList = new ArrayList();
+ private ArrayList<ContentObserver> mCoList = new ArrayList();
private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList();
+ private Handler mHandler = new Handler();
@Override
public void createSystemBinders(int count) {
int i = 0;
while (i++ < count) {
- BroadcastReceiver br = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
-
- }
- };
- IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
- synchronized (mBrList) {
- mBrList.add(br);
+ final ContentObserver co = new ContentObserver(mHandler) {};
+ synchronized (mCoList) {
+ mCoList.add(co);
}
- registerReceiver(br, filt);
+ getContentResolver().registerContentObserver(
+ Settings.System.CONTENT_URI, false, co);
}
}
@@ -74,11 +71,11 @@
public void releaseSystemBinders(int count) {
int i = 0;
while (i++ < count) {
- BroadcastReceiver br;
- synchronized (mBrList) {
- br = mBrList.remove(0);
+ ContentObserver co;
+ synchronized (mCoList) {
+ co = mCoList.remove(0);
}
- unregisterReceiver(br);
+ getContentResolver().unregisterContentObserver(co);
}
}
@@ -117,9 +114,9 @@
@Override
public void releaseAllBinders() {
- synchronized (mBrList) {
- while (mBrList.size() > 0) {
- unregisterReceiver(mBrList.remove(0));
+ synchronized (mCoList) {
+ while (mCoList.size() > 0) {
+ getContentResolver().unregisterContentObserver(mCoList.remove(0));
}
}
synchronized (mTrcList) {
@@ -179,4 +176,4 @@
public IBinder onBind(Intent intent) {
return mBinder;
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
index 6bed2a2..0f1accc 100644
--- a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
@@ -55,8 +55,8 @@
}
@Override
- public void setBinderProxyWatermarks(int high, int low) {
- BinderInternal.nSetBinderProxyCountWatermarks(high, low);
+ public void setBinderProxyWatermarks(int high, int low, int warning) {
+ BinderInternal.nSetBinderProxyCountWatermarks(high, low, warning);
}
@Override
@@ -68,12 +68,23 @@
public void setBinderProxyCountCallback(IBpcCallbackObserver observer) {
if (observer != null) {
BinderInternal.setBinderProxyCountCallback(
- new BinderInternal.BinderProxyLimitListener() {
+ new BinderInternal.BinderProxyCountEventListener() {
@Override
public void onLimitReached(int uid) {
try {
synchronized (observer) {
- observer.onCallback(uid);
+ observer.onLimitReached(uid);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+
+ @Override
+ public void onWarningThresholdReached(int uid) {
+ try {
+ synchronized (observer) {
+ observer.onWarningThresholdReached(uid);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
@@ -98,4 +109,4 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
}
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
index c4ebd56..ada7d92 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
@@ -17,5 +17,6 @@
package com.android.frameworks.coretests.aidl;
interface IBpcCallbackObserver {
- void onCallback(int uid);
-}
\ No newline at end of file
+ void onLimitReached(int uid);
+ void onWarningThresholdReached(int uid);
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
index abdab41..cdcda9d 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
@@ -20,7 +20,7 @@
interface IBpcTestServiceCmdService {
void forceGc();
int getBinderProxyCount(int uid);
- void setBinderProxyWatermarks(int high, int low);
+ void setBinderProxyWatermarks(int high, int low, int warning);
void enableBinderProxyLimit(boolean enable);
void setBinderProxyCountCallback(IBpcCallbackObserver observer);
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/res/layout/activity_abslist_view.xml b/core/tests/coretests/res/layout/activity_abslist_view.xml
new file mode 100644
index 0000000..85b4f11
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_abslist_view.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <view
+ class="android.widget.AbsListViewFunctionalTest$MyListView"
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 0c1e879..6b3cf7b 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -421,6 +421,85 @@
ResourcesManager.setInstance(oriResourcesManager);
}
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testExistingResourcesCreatedByConstructorAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ // Create a Resources through constructor directly before register resources' paths.
+ final DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ final Configuration config = new Configuration();
+ config.setToDefaults();
+ Resources resources = new Resources(new AssetManager(), metrics, config);
+ assertNotNull(resources);
+
+ ResourcesImpl oriResImpl = resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ assertNotSame(oriResImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testNewResourcesWithOutdatedImplAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ Resources old_resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(old_resources);
+ ResourcesImpl oldImpl = old_resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ // Create another resources with identical parameters.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+ // For a normal ResourcesImpl redirect, new Resources may find an old ResourcesImpl cache
+ // and reuse it based on the ResourcesKey. But for shared library ResourcesImpl redirect,
+ // new created Resources should never reuse any old impl, it has to recreate a new impl
+ // which has proper asset paths appended.
+ assertNotSame(oldImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
for (int i = 0; i < resourcePaths.length; i++) {
if (!resourcePaths[i].endsWith(".apk")) {
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
index b61104d..7d7f8ed 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -18,13 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.SensorProps;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -37,8 +34,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.function.Function;
-
@Presubmit
@SmallTest
public class FaceSensorConfigurationsTest {
@@ -48,10 +43,6 @@
private Context mContext;
@Mock
private Resources mResources;
- @Mock
- private IFace mFace;
- @Mock
- private Function<String, IFace> mGetIFace;
private final String[] mAidlInstances = new String[]{"default", "virtual"};
private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
@@ -59,15 +50,13 @@
@Before
public void setUp() throws RemoteException {
- when(mGetIFace.apply(anyString())).thenReturn(mFace);
- when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
when(mContext.getResources()).thenReturn(mResources);
}
@Test
public void testAidlInstanceSensorProps() {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
- mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+ mFaceSensorConfigurations.addAidlConfigs(mAidlInstances);
assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
index f058c16..b2f8299 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -18,13 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorProps;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -37,8 +34,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.function.Function;
-
@Presubmit
@SmallTest
public class FingerprintSensorConfigurationsTest {
@@ -48,10 +43,6 @@
private Context mContext;
@Mock
private Resources mResources;
- @Mock
- private IFingerprint mFingerprint;
- @Mock
- private Function<String, IFingerprint> mGetIFingerprint;
private final String[] mAidlInstances = new String[]{"default", "virtual"};
private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
@@ -59,8 +50,6 @@
@Before
public void setUp() throws RemoteException {
- when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
- when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
when(mContext.getResources()).thenReturn(mResources);
}
@@ -68,7 +57,7 @@
public void testAidlInstanceSensorProps() {
mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
true /* resetLockoutRequiresHardwareAuthToken */);
- mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+ mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances);
assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index bcd9521..84d2995 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -88,9 +88,10 @@
private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
+ private static final int TOO_MANY_BINDERS_WITH_KILL_TIMEOUT_SEC = 30;
- // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
- private static final int BINDER_PROXY_LIMIT = 2500;
+ // Keep in sync with BINDER_PROXY_HIGH_WATERMARK in ActivityManagerService.java
+ private static final int BINDER_PROXY_LIMIT = 6000;
private static Context sContext;
private static UiDevice sUiDevice;
@@ -175,18 +176,26 @@
}
}
- private CountDownLatch createBinderLimitLatch() throws RemoteException {
- final CountDownLatch latch = new CountDownLatch(1);
+ private CountDownLatch[] createBinderLimitLatch() throws RemoteException {
+ final CountDownLatch[] latches = new CountDownLatch[] {
+ new CountDownLatch(1), new CountDownLatch(1)
+ };
sBpcTestServiceCmdService.setBinderProxyCountCallback(
new IBpcCallbackObserver.Stub() {
@Override
- public void onCallback(int uid) {
+ public void onLimitReached(int uid) {
if (uid == sTestPkgUid) {
- latch.countDown();
+ latches[0].countDown();
+ }
+ }
+ @Override
+ public void onWarningThresholdReached(int uid) {
+ if (uid == sTestPkgUid) {
+ latches[1].countDown();
}
}
});
- return latch;
+ return latches;
}
/**
@@ -227,6 +236,7 @@
@Test
public void testBinderProxyLimitBoundary() throws Exception {
final int binderProxyLimit = 2000;
+ final int binderProxyWarning = 1900;
final int rearmThreshold = 1800;
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
@@ -238,19 +248,33 @@
// Get the baseline of binders naturally held by the test Package
int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
- final CountDownLatch binderLimitLatch = createBinderLimitLatch();
- sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+ final CountDownLatch[] binderLatches = createBinderLimitLatch();
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold,
+ binderProxyWarning);
+
+ // Create Binder Proxies up to the warning;
+ sBpcTestAppCmdService.createTestBinders(binderProxyWarning - baseBinderCount);
+ if (binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ + " when proxy warning should not have been triggered");
+ }
+
+ // Create one more Binder to trigger the warning
+ sBpcTestAppCmdService.createTestBinders(1);
+ if (!binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to trigger the warning");
+ }
// Create Binder Proxies up to the limit
- sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
- if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ sBpcTestAppCmdService.createTestBinders(binderProxyLimit - binderProxyWarning - 1);
+ if (binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ " when proxy limit should not have been reached");
}
// Create one more Binder to cross the limit
sBpcTestAppCmdService.createTestBinders(1);
- if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ if (!binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
@@ -274,12 +298,20 @@
sBpcTestServiceCmdService.forceGc();
int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
for (int testLimit : testLimits) {
- final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+ final CountDownLatch[] binderLatches = createBinderLimitLatch();
// Change the BinderProxyLimit
- sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10,
+ testLimit - 10);
+
+ // Trigger the new Binder Proxy warning
+ sBpcTestAppCmdService.createTestBinders(testLimit - 9 - baseBinderCount);
+ if (!binderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to trigger the warning");
+ }
+
// Exceed the new Binder Proxy Limit
- sBpcTestAppCmdService.createTestBinders(testLimit + 1);
- if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ sBpcTestAppCmdService.createTestBinders(10);
+ if (!binderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
@@ -297,6 +329,7 @@
public void testRearmCallbackThreshold() throws Exception {
final int binderProxyLimit = 2000;
final int exceedBinderProxyLimit = binderProxyLimit + 10;
+ final int binderProxyWarning = 1900;
final int rearmThreshold = 1800;
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
@@ -305,11 +338,19 @@
sBpcTestServiceCmdService.enableBinderProxyLimit(true);
sBpcTestServiceCmdService.forceGc();
- final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
- sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+ int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ final CountDownLatch[] firstBinderLatches = createBinderLimitLatch();
+ sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold,
+ binderProxyWarning);
+ // Trigger the Binder Proxy Waring
+ sBpcTestAppCmdService.createTestBinders(binderProxyWarning - baseBinderCount + 1);
+ if (!firstBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to trigger warning");
+ }
+
// Exceed the Binder Proxy Limit
- sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
- if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - binderProxyWarning);
+ if (!firstBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
@@ -321,11 +362,20 @@
sBpcTestServiceCmdService.forceGc();
currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
- final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
+ final CountDownLatch[] secondBinderLatches = createBinderLimitLatch();
+
+ // Exceed the Binder Proxy warning which should not cause a callback since there has
+ // been no rearm
+ sBpcTestAppCmdService.createTestBinders(binderProxyWarning - currentBinderCount + 1);
+ if (secondBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ + " when the callback has not been rearmed yet");
+ }
+
// Exceed the Binder Proxy limit which should not cause a callback since there has
// been no rearm
- sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
- if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - binderProxyWarning);
+ if (secondBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ " when the callback has not been rearmed yet");
}
@@ -337,10 +387,16 @@
sBpcTestServiceCmdService.forceGc();
currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+ // Trigger the Binder Proxy Waring
+ sBpcTestAppCmdService.createTestBinders(binderProxyWarning - currentBinderCount + 1);
+ if (!secondBinderLatches[1].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for uid " + sTestPkgUid + " to trigger warning");
+ }
+
// Exceed the Binder Proxy limit for the last time
sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
- if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ if (!secondBinderLatches[0].await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
@@ -373,7 +429,7 @@
// is not unexpected
}
- if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ if (!binderDeathLatch.await(TOO_MANY_BINDERS_WITH_KILL_TIMEOUT_SEC, TimeUnit.SECONDS)) {
sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 226629e..dcdb8b0 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -19,7 +19,6 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_DEFAULT_NORMAL_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
@@ -137,8 +136,8 @@
mActivityRule.runOnUiThread(() -> {
float density = mActivity.getResources().getDisplayMetrics().density;
ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
- layoutParams.height = (int) (40 * density);
- layoutParams.width = (int) (40 * density);
+ layoutParams.height = 4 * ((int) (10 * density));
+ layoutParams.width = 4 * ((int) (10 * density));
mMovingView.setLayoutParams(layoutParams);
mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
});
@@ -212,8 +211,8 @@
mActivityRule.runOnUiThread(() -> {
float density = mActivity.getResources().getDisplayMetrics().density;
ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
- layoutParams.height = (int) (40 * density);
- layoutParams.width = (int) Math.ceil(41 * density);
+ layoutParams.height = 4 * ((int) (10 * density));
+ layoutParams.width = 4 * ((int) Math.ceil(10 * density)) + 1;
mMovingView.setLayoutParams(layoutParams);
mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
});
@@ -237,8 +236,8 @@
mActivityRule.runOnUiThread(() -> {
float density = mActivity.getResources().getDisplayMetrics().density;
ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
- layoutParams.height = (int) Math.ceil(41 * density);
- layoutParams.width = (int) (40 * density);
+ layoutParams.height = 4 * ((int) Math.ceil(10 * density)) + 1;
+ layoutParams.width = 4 * ((int) (10 * density));
mMovingView.setLayoutParams(layoutParams);
mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
});
@@ -256,13 +255,14 @@
}
@Test
- @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_DEFAULT_NORMAL_READ_ONLY})
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void defaultNormal() throws Throwable {
waitForFrameRateCategoryToSettle();
mActivityRule.runOnUiThread(() -> {
mMovingView.invalidate();
- assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ assertEquals(expected,
mViewRoot.getPreferredFrameRateCategory());
});
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index fa364e0..a034f3b 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -667,7 +667,7 @@
}
/**
- * Test how values of the frame rate cateogry are aggregated.
+ * Test how values of the frame rate category are aggregated.
* It should take the max value among all of the voted categories per frame.
*/
@Test
@@ -1067,18 +1067,32 @@
/**
* Test the IsFrameRatePowerSavingsBalanced values are properly set
*/
- @UiThreadTest
@Test
@Ignore("Can be enabled only after b/330596920 is ready")
@RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
- ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
- sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true);
- viewRootImpl.setFrameRatePowerSavingsBalanced(false);
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), false);
- viewRootImpl.setFrameRatePowerSavingsBalanced(true);
- assertEquals(viewRootImpl.isFrameRatePowerSavingsBalanced(), true);
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRoot = view.getViewRootImpl();
+ final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+ assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true);
+ assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
+ attrs.isFrameRatePowerSavingsBalanced());
+
+ sInstrumentation.runOnMainSync(() -> {
+ attrs.setFrameRatePowerSavingsBalanced(false);
+ viewRoot.setLayoutParams(attrs, false);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes;
+ assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), false);
+ assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
+ newAttrs.isFrameRatePowerSavingsBalanced());
+ });
}
/**
diff --git a/core/tests/coretests/src/android/widget/AbsListViewActivity.java b/core/tests/coretests/src/android/widget/AbsListViewActivity.java
new file mode 100644
index 0000000..a617fa4
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AbsListViewActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the AbsListView widget.
+ */
+public class AbsListViewActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_abslist_view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
new file mode 100644
index 0000000..ceea6ca
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class AbsListViewFunctionalTest {
+ private final String[] mCountryList = new String[] {
+ "Argentina", "Australia", "Belize", "Botswana", "Brazil", "Cameroon", "China", "Cyprus",
+ "Denmark", "Djibouti", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Germany",
+ "Ghana", "Haiti", "Honduras", "Iceland", "India", "Indonesia", "Ireland", "Italy",
+ "Japan", "Kiribati", "Laos", "Lesotho", "Liberia", "Malaysia", "Mongolia", "Myanmar",
+ "Nauru", "Norway", "Oman", "Pakistan", "Philippines", "Portugal", "Romania", "Russia",
+ "Rwanda", "Singapore", "Slovakia", "Slovenia", "Somalia", "Swaziland", "Togo", "Tuvalu",
+ "Uganda", "Ukraine", "United States", "Vanuatu", "Venezuela", "Zimbabwe"
+ };
+ private AbsListViewActivity mActivity;
+ private MyListView mMyListView;
+
+ @Rule
+ public ActivityTestRule<AbsListViewActivity> mActivityRule = new ActivityTestRule<>(
+ AbsListViewActivity.class);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = mActivityRule.getActivity();
+ mMyListView = (MyListView) mActivity.findViewById(R.id.list_view);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testLsitViewSetVelocity() throws Throwable {
+ final ArrayList<String> items = new ArrayList<>(Arrays.asList(mCountryList));
+ final ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
+ android.R.layout.simple_list_item_1, items);
+
+ WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mMyListView,
+ () -> mMyListView.setAdapter(adapter));
+ mActivityRule.runOnUiThread(() -> {
+ // Create an adapter to display the list
+ mMyListView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyListView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyListView.fling(100);
+ });
+ PollingCheck.waitFor(100, () -> mMyListView.isSetVelocityCalled);
+ // set setFrameContentVelocity should be called when fling.
+ assertTrue(mMyListView.isSetVelocityCalled);
+ }
+
+ public static class MyListView extends ListView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyListView(Context context) {
+ super(context);
+ }
+
+ public MyListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a709d7b..6321e5d 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -96,7 +96,7 @@
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
mDispatcher = new WindowOnBackInvokedDispatcher(mContext);
- mDispatcher.attachToWindow(mWindowSession, mWindow);
+ mDispatcher.attachToWindow(mWindowSession, mWindow, null);
}
private void waitForIdle() {
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 745390d..9f5ed29 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -53,7 +53,6 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.support.test.uiautomator.By;
@@ -63,7 +62,6 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Button;
@@ -298,7 +296,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN)
public void createDialog_onLockscreen_hasExpectedContent() {
when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
launchActivity();
diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
index 68545cf..ad4ccc9 100644
--- a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java
@@ -153,4 +153,41 @@
final String[] actual = connectivityBlobStore.list(TEST_NAME /* prefix */);
assertArrayEquals(expected, actual);
}
+
+ @Test
+ public void testList_underscoreInPrefix() throws Exception {
+ final String prefix = TEST_NAME + "_";
+ final String[] unsortedNames = new String[] {
+ prefix + "000",
+ TEST_NAME + "123",
+ };
+ // The '_' in the prefix should not be treated as a wildcard so the only match is "000".
+ final String[] expected = new String[] {"000"};
+ final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+ for (int i = 0; i < unsortedNames.length; i++) {
+ assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+ }
+ final String[] actual = connectivityBlobStore.list(prefix);
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testList_percentInPrefix() throws Exception {
+ final String prefix = "%" + TEST_NAME + "%";
+ final String[] unsortedNames = new String[] {
+ TEST_NAME + "12345",
+ prefix + "0",
+ "abc" + TEST_NAME + "987",
+ };
+ // The '%' in the prefix should not be treated as a wildcard so the only match is "0".
+ final String[] expected = new String[] {"0"};
+ final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore();
+
+ for (int i = 0; i < unsortedNames.length; i++) {
+ assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB));
+ }
+ final String[] actual = connectivityBlobStore.list(prefix);
+ assertArrayEquals(expected, actual);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index b9841ff..82251b8 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -182,7 +182,8 @@
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
+ wmlp.setFrameRatePowerSavingsBalanced(
+ mPhoneWindow.getAttributes().isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.addView(decorView, wmlp);
@@ -203,7 +204,8 @@
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
+ wmlp.setFrameRatePowerSavingsBalanced(
+ mPhoneWindow.getAttributes().isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.addView(decorView, wmlp);
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 0897726..cf7c549 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -38,6 +38,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -73,8 +74,8 @@
@Test
public void create() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -87,8 +88,8 @@
@Test
public void equals() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -100,15 +101,14 @@
Assert.assertEquals(info, sameInfo);
final DeviceStateInfo differentInfo = new DeviceStateInfo(
- List.of(DEVICE_STATE_0, DEVICE_STATE_2), baseState,
- currentState);
+ new ArrayList<>(List.of(DEVICE_STATE_0, DEVICE_STATE_2)), baseState, currentState);
assertNotEquals(info, differentInfo);
}
@Test
public void diff_sameObject() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState baseState = DEVICE_STATE_0;
final DeviceState currentState = DEVICE_STATE_2;
@@ -118,10 +118,10 @@
@Test
public void diff_differentSupportedStates() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
- DEVICE_STATE_0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_2),
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
DEVICE_STATE_0, DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_2)), DEVICE_STATE_0, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -130,10 +130,10 @@
@Test
public void diff_differentNonOverrideState() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_1,
- DEVICE_STATE_0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
- DEVICE_STATE_2, DEVICE_STATE_0);
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
+ DEVICE_STATE_1, DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_2, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -142,10 +142,10 @@
@Test
public void diff_differentState() {
- final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
- DEVICE_STATE_1);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
- DEVICE_STATE_0, DEVICE_STATE_2);
+ final DeviceStateInfo info = new DeviceStateInfo(new ArrayList<>(List.of(DEVICE_STATE_1)),
+ DEVICE_STATE_0, DEVICE_STATE_1);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(
+ new ArrayList<>(List.of(DEVICE_STATE_1)), DEVICE_STATE_0, DEVICE_STATE_2);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -154,8 +154,8 @@
@Test
public void writeToParcel() {
- final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
- DEVICE_STATE_2);
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_1, DEVICE_STATE_2));
final DeviceState nonOverrideState = DEVICE_STATE_0;
final DeviceState state = DEVICE_STATE_2;
final DeviceStateInfo originalInfo =
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index ee238c0..f4d3631 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -40,6 +40,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -276,7 +277,7 @@
new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build());
final DeviceState state = new DeviceState(
new DeviceState.Configuration.Builder(mergedState, "" /* name */).build());
- return new DeviceStateInfo(mSupportedDeviceStates, baseState, state);
+ return new DeviceStateInfo(new ArrayList<>(mSupportedDeviceStates), baseState, state);
}
private void notifyDeviceStateInfoChanged() {
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 685fd82..199e929 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -140,7 +140,11 @@
* value of the supported `wght`axis, the maximum supported `wght` value is used. The weight
* value of the font is ignored.
*
- * If none of the above conditions are met, this function return {@code null}.
+ * If none of the above conditions are met, the provided font files cannot be used for
+ * variable font family and this function returns {@code null}. Even if this function
+ * returns {@code null}, you can still use {@link #build()} method for creating FontFamily
+ * instance with manually specifying variation settings by using
+ * {@link Font.Builder#setFontVariationSettings(String)}.
*
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 8829d1b..b749a06 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -45,7 +45,6 @@
name: "wm_shell_util-sources",
srcs: [
"src/com/android/wm/shell/animation/Interpolators.java",
- "src/com/android/wm/shell/animation/PhysicsAnimator.kt",
"src/com/android/wm/shell/common/bubbles/*.kt",
"src/com/android/wm/shell/common/bubbles/*.java",
"src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
@@ -166,10 +165,28 @@
},
}
+filegroup {
+ name: "wm_shell-shared-aidls",
+
+ srcs: [
+ "shared/**/*.aidl",
+ ],
+
+ path: "shared/src",
+}
+
java_library {
name: "WindowManager-Shell-shared",
- srcs: ["shared/**/*.java"],
+ srcs: [
+ "shared/**/*.java",
+ "shared/**/*.kt",
+ ":wm_shell-shared-aidls",
+ ],
+ static_libs: [
+ "androidx.dynamicanimation_dynamicanimation",
+ "jsr330",
+ ],
}
android_library {
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index f3d70f7..35a4a62 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -34,11 +34,11 @@
import com.android.internal.protolog.common.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl
similarity index 91%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl
index 72fba3b..8481c44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IHomeTransitionListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
similarity index 93%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
index 7f4a8f1..526407e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.view.SurfaceControl;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
-import com.android.wm.shell.transition.IHomeTransitionListener;
+import com.android.wm.shell.shared.IHomeTransitionListener;
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
similarity index 87%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
index da39017..5e49f55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.shared;
import android.annotation.NonNull;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface to manage remote transitions.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimator.kt
similarity index 99%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimator.kt
index b7f0890..9d3b56d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimator.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.animation
+package com.android.wm.shell.shared.animation
import android.util.ArrayMap
import android.util.Log
@@ -25,7 +25,7 @@
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.wm.shell.animation.PhysicsAnimator.Companion.getInstance
+import com.android.wm.shell.shared.animation.PhysicsAnimator.Companion.getInstance
import java.lang.ref.WeakReference
import java.util.WeakHashMap
import kotlin.math.abs
@@ -874,7 +874,7 @@
*
* @param <T> The type of the object being animated.
</T> */
- interface UpdateListener<T> {
+ fun interface UpdateListener<T> {
/**
* Called on each animation frame with the target object, and a map of FloatPropertyCompat
@@ -904,7 +904,7 @@
*
* @param <T> The type of the object being animated.
</T> */
- interface EndListener<T> {
+ fun interface EndListener<T> {
/**
* Called with the final animation values as each property animation ends. This can be used
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index 7defc26..235b9bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.animation
+package com.android.wm.shell.shared.animation
import android.os.Handler
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.prepareForTest
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
similarity index 74%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
index c2fd54f..a1496ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
-
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
@@ -24,10 +23,12 @@
import javax.inject.Qualifier;
-/** Annotates a method or qualifies a provider that runs on the Shell splashscreen-thread */
+/**
+ * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
+ * instead of the app vsync.
+ */
@Documented
@Inherited
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellSplashscreenThread {
-}
+public @interface ChoreographerSfVsync {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java
similarity index 89%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java
index 9ac7a12..52a717b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalMainThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalMainThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java
similarity index 77%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java
index 4cd3c90..ae5188c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
-
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
@@ -24,10 +23,9 @@
import javax.inject.Qualifier;
-/** Annotates a method or qualifies a provider that runs on the shared background thread */
+/** Annotates a method or class that is called from an external thread to the Shell threads. */
@Documented
@Inherited
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellBackgroundThread {
-}
+public @interface ExternalThread {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java
similarity index 83%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java
index 4cd3c90..bd2887e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
-
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
@@ -24,10 +23,9 @@
import javax.inject.Qualifier;
-/** Annotates a method or qualifies a provider that runs on the shared background thread */
+/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
@Documented
@Inherited
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellBackgroundThread {
-}
+public @interface ShellAnimationThread {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java
similarity index 90%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java
index 4cd3c90..586ac82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellBackgroundThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java
similarity index 83%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java
index 4cd3c90..6c879a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellBackgroundThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
-
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
@@ -24,10 +23,9 @@
import javax.inject.Qualifier;
-/** Annotates a method or qualifies a provider that runs on the shared background thread */
+/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
@Documented
@Inherited
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellBackgroundThread {
-}
+public @interface ShellMainThread {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java
similarity index 90%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java
index c2fd54f..4887dbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellSplashscreenThread.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellSplashscreenThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.annotations;
+package com.android.wm.shell.shared.annotations;
import java.lang.annotation.Documented;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8d8dc10..2643211 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -20,7 +20,7 @@
import android.view.MotionEvent;
import android.window.BackEvent;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface for external process to get access to the Back animation related methods.
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 9b9798c..59c841f 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
@@ -69,8 +69,8 @@
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
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.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -686,7 +686,11 @@
}
}
- private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ if (!mOnBackStartDispatched) {
+ Log.e(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched.");
+ return;
+ }
if (callback == null) {
return;
}
@@ -745,7 +749,7 @@
if (touchTracker.getTriggerBack()) {
dispatchOrAnimateOnBackInvoked(callback, touchTracker);
} else {
- dispatchOnBackCancelled(callback);
+ tryDispatchOnBackCancelled(callback);
}
}
finishBackNavigation(touchTracker.getTriggerBack());
@@ -824,7 +828,7 @@
if (mCurrentTracker.getTriggerBack()) {
dispatchOrAnimateOnBackInvoked(mActiveCallback, mCurrentTracker);
} else {
- dispatchOnBackCancelled(mActiveCallback);
+ tryDispatchOnBackCancelled(mActiveCallback);
}
}
@@ -876,7 +880,7 @@
if (mCurrentTracker.isInInitialState()) {
if (mBackGestureStarted) {
mBackGestureStarted = false;
- dispatchOnBackCancelled(mActiveCallback);
+ tryDispatchOnBackCancelled(mActiveCallback);
finishBackNavigation(false);
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"resetTouchTracker -> reset an unfinished gesture");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 7561a26..772eae7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -42,8 +42,8 @@
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators
-import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.annotations.ShellMainThread
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
@@ -65,7 +65,7 @@
private val targetEnteringRect = RectF()
private val currentEnteringRect = RectF()
- private val taskBoundsRect = Rect()
+ private val backAnimRect = Rect()
private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
@@ -109,10 +109,10 @@
transaction.setAnimationTransaction()
// Offset start rectangle to align task bounds.
- taskBoundsRect.set(closingTarget!!.windowConfiguration.bounds)
- taskBoundsRect.offsetTo(0, 0)
+ backAnimRect.set(closingTarget!!.localBounds)
+ backAnimRect.offsetTo(0, 0)
- startClosingRect.set(taskBoundsRect)
+ startClosingRect.set(backAnimRect)
// scale closing target into the middle for rhs and to the right for lhs
targetClosingRect.set(startClosingRect)
@@ -154,7 +154,7 @@
}
private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
- val screenHeight = taskBoundsRect.height()
+ val screenHeight = backAnimRect.height()
// Base the window movement in the Y axis on the touch movement in the Y axis.
val rawYDelta = touchY - initialTouchPos.y
val yDirection = (if (rawYDelta < 0) -1 else 1)
@@ -181,8 +181,8 @@
// off the animator
startClosingRect.set(currentClosingRect)
startEnteringRect.set(currentEnteringRect)
- targetEnteringRect.set(taskBoundsRect)
- targetClosingRect.set(taskBoundsRect)
+ targetEnteringRect.set(backAnimRect)
+ targetClosingRect.set(backAnimRect)
targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION)
@@ -240,13 +240,13 @@
private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
if (leash == null || !leash.isValid) return
- val scale = rect.width() / taskBoundsRect.width()
+ val scale = rect.width() / backAnimRect.width()
transformMatrix.reset()
transformMatrix.setScale(scale, scale)
transformMatrix.postTranslate(rect.left, rect.top)
transaction.setAlpha(leash, alpha)
.setMatrix(leash, transformMatrix, tmpFloat9)
- .setCrop(leash, taskBoundsRect)
+ .setCrop(leash, backAnimRect)
.setCornerRadius(leash, cornerRadius)
}
@@ -267,6 +267,7 @@
transaction
.setColor(scrimLayer, colorComponents)
.setAlpha(scrimLayer!!, maxScrimAlpha)
+ .setCrop(scrimLayer!!, closingTarget!!.localBounds)
.setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1)
.show(scrimLayer)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index cfd9fb6..cae2e80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -49,7 +49,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index fcf500a..e33aa75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -54,7 +54,7 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 4455a3c..ce8a460 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -101,14 +101,14 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 6908682..8da85d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -82,7 +82,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
@@ -95,6 +94,7 @@
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.bubbles.RelativeTouchListener;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
import java.io.PrintWriter;
import java.math.BigDecimal;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 26077cf..127a49f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,8 +37,8 @@
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 512c9d1..1fb966f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -34,12 +34,12 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BadgedImageView;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.google.android.collect.Sets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index bb0dd95..47d4d07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -38,12 +38,12 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BadgedImageView;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.google.android.collect.Sets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 9eb9632..8af4c75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -43,12 +43,12 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
/**
* Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 81e7582..02918db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -29,8 +29,8 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index ee552ae..e108f7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -28,7 +28,6 @@
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.wm.shell.R
-import com.android.wm.shell.animation.PhysicsAnimator
import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
@@ -37,6 +36,7 @@
import com.android.wm.shell.bubbles.setup
import com.android.wm.shell.common.bubbles.BubblePopupDrawable
import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.shared.animation.PhysicsAnimator
import kotlin.math.roundToInt
/** Manages bubble education presentation and animation */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index b828aac..2873d584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -28,7 +28,7 @@
import androidx.annotation.BinderThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 8353900..dcbc72a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -34,7 +34,7 @@
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index ca06024..55dc793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -30,7 +30,7 @@
import androidx.annotation.BinderThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index 53683c6..43c92ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -33,7 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
deleted file mode 100644
index 4009ad2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ChoreographerSfVsync.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/**
- * Annotates a method that or qualifies a provider runs aligned to the Choreographer SF vsync
- * instead of the app vsync.
- */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ChoreographerSfVsync {}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
deleted file mode 100644
index 7560f71..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ExternalThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or class that is called from an external thread to the Shell threads. */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ExternalThread {}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
deleted file mode 100644
index 0479f87..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellAnimationThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or qualifies a provider that runs on the Shell animation-thread */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellAnimationThread {}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
deleted file mode 100644
index 423f4ce..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/annotations/ShellMainThread.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.wm.shell.common.annotations;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Qualifier;
-
-/** Annotates a method or qualifies a provider that runs on the Shell main-thread */
-@Documented
-@Inherited
-@Qualifier
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ShellMainThread {}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
index 9094739..e06de9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -35,7 +35,7 @@
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.shared.animation.PhysicsAnimator
/**
* View that handles interactions between DismissCircleView and BubbleStackView.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 11e4777..123d4dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -28,7 +28,7 @@
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
-import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.shared.animation.PhysicsAnimator
import kotlin.math.abs
import kotlin.math.hypot
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
index 317e48e..c421dec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
@@ -28,7 +28,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
import java.util.Map;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index fa2e236..cf3ad42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -24,8 +24,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import javax.inject.Inject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 216da07..0110937 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -31,10 +31,9 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.pip.TvPipModule;
-import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.tv.TvSplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
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 5122114..73228de 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
@@ -58,10 +58,6 @@
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -92,6 +88,11 @@
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -106,7 +107,6 @@
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.HomeTransitionObserver;
-import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 0cc545a..c5644a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -33,11 +33,11 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalMainThread;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.shared.annotations.ExternalMainThread;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import dagger.Module;
import dagger.Provides;
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 04f0f44..b933e5d 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
@@ -52,9 +52,6 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellAnimationThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
@@ -77,6 +74,9 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.annotations.ShellAnimationThread;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 1e3d7fb..d644006 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -29,7 +29,6 @@
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -56,6 +55,7 @@
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 458ea05..b86e39f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -25,7 +25,6 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -38,6 +37,7 @@
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -93,8 +93,9 @@
@Provides
static PipScheduler providePipScheduler(Context context,
PipBoundsState pipBoundsState,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipScheduler(context, pipBoundsState, mainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor,
+ ShellTaskOrganizer shellTaskOrganizer) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor, shellTaskOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 54c2aea..8d1b15c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -29,7 +29,6 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -53,6 +52,7 @@
import com.android.wm.shell.pip.tv.TvPipNotificationController;
import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 5889da1..df1b062 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,7 +18,7 @@
import android.graphics.Region;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 7c8fcbb..99a00b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -20,6 +20,7 @@
import android.util.ArrayMap
import android.util.ArraySet
import android.util.SparseArray
+import android.view.Display.INVALID_DISPLAY
import androidx.core.util.forEach
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
@@ -226,6 +227,14 @@
displayData[otherDisplayId].visibleTasks.size)
}
}
+ } else if (displayId == INVALID_DISPLAY) {
+ // Task has vanished. Check which display to remove the task from.
+ displayData.forEach { displayId, data ->
+ if (data.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+ }
+ }
+ return
}
val prevCount = getVisibleTaskCount(displayId)
@@ -236,6 +245,7 @@
}
val newCount = getVisibleTaskCount(displayId)
+ // Check if count changed
if (prevCount != newCount) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
@@ -244,10 +254,6 @@
visible,
displayId
)
- }
-
- // Check if count changed
- if (prevCount != newCount) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 1b1c967..c369061 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -61,8 +61,6 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.annotations.ExternalThread
-import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.compatui.isSingleTopActivityTranslucent
@@ -72,6 +70,8 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.shared.annotations.ExternalThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 7da1b23..165feec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -67,8 +67,8 @@
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 73de231..863a51a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -48,8 +48,8 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index 33c299f..4215b2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.window.IRemoteTransition;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface exposed to SystemUI Keyguard to register handlers for running
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 2ee3348..b000e32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -18,7 +18,7 @@
import android.os.SystemProperties;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
/**
* Interface to engage one handed feature.
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 679d4ca..39b9000 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
@@ -55,7 +55,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
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 a9aa6ba..7b1ef5c 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
@@ -18,7 +18,7 @@
import android.graphics.Rect;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.function.Consumer;
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 bd186ba..cdeb00b 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
@@ -82,7 +82,6 @@
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -92,6 +91,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
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 df67707..ef46843 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
@@ -37,7 +37,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
-import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
@@ -47,6 +46,7 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 4c69cc3..1e18b8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -245,9 +245,9 @@
Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onSwipePipToHomeAnimationStart: %s", componentName);
- mPipScheduler.setInSwipePipToHomeTransition(true);
+ mPipScheduler.onSwipePipToHomeAnimationStart(taskId, componentName, destinationBounds,
+ overlay, appBounds);
mPipRecentsAnimationListener.onPipAnimationStarted();
- // TODO: cache the overlay if provided for reparenting later.
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 6665013..b4ca7df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -21,6 +21,7 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -30,9 +31,11 @@
import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
@@ -52,6 +55,7 @@
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private PipSchedulerReceiver mSchedulerReceiver;
private PipTransitionController mPipTransitionController;
@@ -66,6 +70,16 @@
// true if Launcher has started swipe PiP to home animation
private boolean mInSwipePipToHomeTransition;
+ // Overlay leash potentially used during swipe PiP to home transition;
+ // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
+ @Nullable
+ SurfaceControl mSwipePipToHomeOverlay;
+
+ // App bounds used when as a starting point to swipe PiP to home animation in Launcher;
+ // these are also used to calculate the app icon overlay buffer size.
+ @NonNull
+ final Rect mSwipePipToHomeAppBounds = new Rect();
+
/**
* Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
* This is used for a broadcast receiver to resolve intents. This should be removed once
@@ -101,11 +115,14 @@
}
}
- public PipScheduler(Context context, PipBoundsState pipBoundsState,
- ShellExecutor mainExecutor) {
+ public PipScheduler(Context context,
+ PipBoundsState pipBoundsState,
+ ShellExecutor mainExecutor,
+ ShellTaskOrganizer shellTaskOrganizer) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
+ mShellTaskOrganizer = shellTaskOrganizer;
if (PipUtils.isPip2ExperimentEnabled()) {
// temporary broadcast receiver to initiate exit PiP via expand
@@ -115,6 +132,10 @@
}
}
+ ShellExecutor getMainExecutor() {
+ return mMainExecutor;
+ }
+
void setPipTransitionController(PipTransitionController pipTransitionController) {
mPipTransitionController = pipTransitionController;
}
@@ -171,6 +192,24 @@
mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
}
+ void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ mInSwipePipToHomeTransition = true;
+ mSwipePipToHomeOverlay = overlay;
+ mSwipePipToHomeAppBounds.set(appBounds);
+ if (overlay != null) {
+ // Shell transitions might use a root animation leash, which will be removed when
+ // the Recents transition is finished. Launcher attaches the overlay leash to this
+ // animation target leash; thus, we need to reparent it to the actual Task surface now.
+ // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
+ // transition.
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx);
+ tx.setLayer(overlay, Integer.MAX_VALUE);
+ tx.apply();
+ }
+ }
+
void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
mInSwipePipToHomeTransition = inSwipePipToHome;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index d15da4a..b179b5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -24,6 +24,9 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
@@ -44,6 +47,7 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipContentOverlay;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -55,6 +59,11 @@
*/
public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ /**
+ * The fixed start delay in ms when fading out the content overlay from bounds animation.
+ * The fadeout animation is guaranteed to start after the client has drawn under the new config.
+ */
+ private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
private final Context mContext;
private final PipScheduler mPipScheduler;
@@ -230,10 +239,13 @@
PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
Rect srcRectHint = params.getSourceRectHint();
+ Rect startBounds = pipChange.getStartAbsBounds();
Rect destinationBounds = pipChange.getEndAbsBounds();
+ WindowContainerTransaction finishWct = new WindowContainerTransaction();
+
if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
- float scale = (float) destinationBounds.width() / srcRectHint.width();
+ final float scale = (float) destinationBounds.width() / srcRectHint.width();
startTransaction.setWindowCrop(pipLeash, srcRectHint);
startTransaction.setPosition(pipLeash,
destinationBounds.left - srcRectHint.left * scale,
@@ -244,13 +256,62 @@
// in multi-activity case, reparenting yields new reset scales coming from pinned task.
startTransaction.setScale(pipLeash, scale, scale);
} else {
- // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay).
+ final float scaleX = (float) destinationBounds.width() / startBounds.width();
+ final float scaleY = (float) destinationBounds.height() / startBounds.height();
+ final int overlaySize = PipContentOverlay.PipAppIconOverlay
+ .getOverlaySize(mPipScheduler.mSwipePipToHomeAppBounds, destinationBounds);
+ SurfaceControl overlayLeash = mPipScheduler.mSwipePipToHomeOverlay;
+
+ startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
+ .setScale(pipLeash, scaleX, scaleY)
+ .setWindowCrop(pipLeash, startBounds)
+ .reparent(overlayLeash, pipLeash)
+ .setLayer(overlayLeash, Integer.MAX_VALUE);
+
+ if (mPipTaskToken != null) {
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
+ this::onClientDrawAtTransitionEnd)
+ .setScale(overlayLeash, 1f, 1f)
+ .setPosition(overlayLeash,
+ (destinationBounds.width() - overlaySize) / 2f,
+ (destinationBounds.height() - overlaySize) / 2f);
+ finishWct.setBoundsChangeTransaction(mPipTaskToken, tx);
+ }
}
startTransaction.apply();
- finishCallback.onTransitionFinished(null);
+
+ // Note that finishWct should be free of any actual WM state changes; we are using
+ // it for syncing with the client draw after delayed configuration changes are dispatched.
+ finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
return true;
}
+ private void onClientDrawAtTransitionEnd() {
+ startOverlayFadeoutAnimation();
+ }
+
+ private void startOverlayFadeoutAnimation() {
+ ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
+ animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.remove(mPipScheduler.mSwipePipToHomeOverlay);
+ tx.apply();
+ mPipScheduler.mSwipePipToHomeOverlay = null;
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float alpha = (float) animation.getAnimatedValue();
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setAlpha(mPipScheduler.mSwipePipToHomeOverlay, alpha).apply();
+ });
+ animator.start();
+ }
+
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2616b8b..eebd133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.List;
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 3707207..f9fcfac 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
@@ -49,11 +49,11 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2b433e9..5762197 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -21,8 +21,8 @@
import android.app.ActivityManager;
import android.graphics.Rect;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
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 3e34c30..c3261bb 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
@@ -90,7 +90,6 @@
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.common.split.SplitScreenUtils;
@@ -99,6 +98,7 @@
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 4465aef..3353c7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -47,8 +47,8 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
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 2f6edc2..5ced1fb 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,7 +45,7 @@
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
index a7e4b01..f0a2315 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactory.java
@@ -19,7 +19,7 @@
import android.annotation.UiContext;
import android.content.Context;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
index 7eed588..e4fcff0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewFactoryController.java
@@ -22,7 +22,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index cb2944c..c9185ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.TransitionUtil;
/**
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 a77602b..437a00e 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
@@ -76,10 +76,13 @@
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.IHomeTransitionListener;
+import com.android.wm.shell.shared.IShellTransitions;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
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 2919782..d839eae 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
@@ -557,6 +557,23 @@
}
@Test
+ public void skipsCancelWithoutStart() throws RemoteException {
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ doMotionEvent(MotionEvent.ACTION_CANCEL, 0);
+ mShellExecutor.flushAll();
+
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(any());
+ verify(mAppCallback, never()).onBackInvoked();
+ verify(mAppCallback, never()).onBackCancelled();
+ }
+
+ @Test
public void testBackToActivity() throws RemoteException {
final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(
mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index a4fb350..8bb182d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -22,7 +22,7 @@
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 445f74a..9f3a4d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
@@ -237,6 +238,27 @@
assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4)
}
+ /**
+ * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY.
+ * This tests that task is removed from the last parent display when it vanishes.
+ */
+ @Test
+ fun updateVisibleFreeformTasks_removeVisibleTasksRemovesTaskWithInvalidDisplay() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
+ repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
+ executor.flushAll()
+
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+ repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false)
+ executor.flushAll()
+
+ assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3)
+ assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1)
+ }
+
@Test
fun getVisibleTaskCount() {
// No tasks, count is 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTest.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTest.kt
index e727491..3fb66be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.animation
+package com.android.wm.shell.shared.animation
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -27,11 +27,11 @@
import androidx.dynamicanimation.animation.SpringForce
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.animation.PhysicsAnimator.EndListener
-import com.android.wm.shell.animation.PhysicsAnimator.UpdateListener
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
-import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import com.android.wm.shell.shared.animation.PhysicsAnimator.EndListener
+import com.android.wm.shell.shared.animation.PhysicsAnimator.UpdateListener
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 66efa02..e7d37ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 5b4ab5f..efa9b11 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -16,15 +16,16 @@
#include "WebViewFunctorManager.h"
+#include <log/log.h>
#include <private/hwui/WebViewFunctor.h>
+#include <utils/Trace.h>
+
+#include <atomic>
+
#include "Properties.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/RenderThread.h"
-#include <log/log.h>
-#include <utils/Trace.h>
-#include <atomic>
-
namespace android::uirenderer {
namespace {
@@ -265,7 +266,7 @@
}
void WebViewFunctor::reportRenderingThreads(const int32_t* thread_ids, size_t size) {
- // TODO(b/329219352): Pass the threads to HWUI and update the ADPF session.
+ mRenderingThreads = std::vector<int32_t>(thread_ids, thread_ids + size);
}
WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -365,6 +366,21 @@
}
}
+std::vector<int32_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+ std::vector<int32_t> renderingThreads;
+ std::lock_guard _lock{mLock};
+ for (const auto& iter : mActiveFunctors) {
+ const auto& functorThreads = iter->getRenderingThreads();
+ for (const auto& tid : functorThreads) {
+ if (std::find(renderingThreads.begin(), renderingThreads.end(), tid) ==
+ renderingThreads.end()) {
+ renderingThreads.push_back(tid);
+ }
+ }
+ }
+ return renderingThreads;
+}
+
sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
std::lock_guard _lock{mLock};
for (auto& iter : mActiveFunctors) {
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 1bf2c1f..2d77dd8 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -60,6 +60,10 @@
void onRemovedFromTree() { mReference.onRemovedFromTree(); }
+ const std::vector<int32_t>& getRenderingThreads() const {
+ return mReference.getRenderingThreads();
+ }
+
private:
friend class WebViewFunctor;
@@ -82,6 +86,7 @@
void mergeTransaction(ASurfaceTransaction* transaction);
void reportRenderingThreads(const int32_t* thread_ids, size_t size);
+ const std::vector<int32_t>& getRenderingThreads() const { return mRenderingThreads; }
sp<Handle> createHandle() {
LOG_ALWAYS_FATAL_IF(mCreatedHandle);
@@ -102,6 +107,7 @@
bool mCreatedHandle = false;
int32_t mParentSurfaceControlGenerationId = 0;
ASurfaceControl* mSurfaceControl = nullptr;
+ std::vector<int32_t> mRenderingThreads;
};
class WebViewFunctorManager {
@@ -113,6 +119,7 @@
void onContextDestroyed();
void destroyFunctor(int functor);
void reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size);
+ std::vector<int32_t> getRenderingThreadsForActiveFunctors();
sp<WebViewFunctor::Handle> handleFor(int functor);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index abf64d0..1fbd580 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -777,6 +777,8 @@
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+ mHintSessionWrapper->setActiveFunctorThreads(
+ WebViewFunctorManager::instance().getRenderingThreadsForActiveFunctors());
}
mLastDequeueBufferDuration = dequeueBufferDuration;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 2362331..6993d52 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -20,6 +20,7 @@
#include <private/performance_hint_private.h>
#include <utils/Log.h>
+#include <algorithm>
#include <chrono>
#include <vector>
@@ -49,6 +50,7 @@
BIND_APH_METHOD(updateTargetWorkDuration);
BIND_APH_METHOD(reportActualWorkDuration);
BIND_APH_METHOD(sendHint);
+ BIND_APH_METHOD(setThreads);
mInitialized = true;
}
@@ -67,6 +69,10 @@
mHintSession = mHintSessionFuture->get();
mHintSessionFuture = std::nullopt;
}
+ if (mSetThreadsFuture.has_value()) {
+ mSetThreadsFuture->wait();
+ mSetThreadsFuture = std::nullopt;
+ }
if (mHintSession) {
mBinding->closeSession(mHintSession);
mSessionValid = true;
@@ -106,16 +112,16 @@
APerformanceHintManager* manager = mBinding->getManager();
if (!manager) return false;
- std::vector<pid_t> tids = CommonPool::getThreadIds();
- tids.push_back(mUiThreadId);
- tids.push_back(mRenderThreadId);
+ mPermanentSessionTids = CommonPool::getThreadIds();
+ mPermanentSessionTids.push_back(mUiThreadId);
+ mPermanentSessionTids.push_back(mRenderThreadId);
// Use the cached target value if there is one, otherwise use a default. This is to ensure
// the cached target and target in PowerHAL are consistent, and that it updates correctly
// whenever there is a change.
int64_t targetDurationNanos =
mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
- mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
+ mHintSessionFuture = CommonPool::async([=, this, tids = mPermanentSessionTids] {
return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
@@ -143,6 +149,23 @@
mLastFrameNotification = systemTime();
}
+void HintSessionWrapper::setActiveFunctorThreads(std::vector<pid_t> threadIds) {
+ if (!init()) return;
+ if (!mBinding || !mHintSession) return;
+ // Sort the vector to make sure they're compared as sets.
+ std::sort(threadIds.begin(), threadIds.end());
+ if (threadIds == mActiveFunctorTids) return;
+ mActiveFunctorTids = std::move(threadIds);
+ std::vector<pid_t> combinedTids = mPermanentSessionTids;
+ std::copy(mActiveFunctorTids.begin(), mActiveFunctorTids.end(),
+ std::back_inserter(combinedTids));
+ mSetThreadsFuture = CommonPool::async([this, tids = std::move(combinedTids)] {
+ int ret = mBinding->setThreads(mHintSession, tids.data(), tids.size());
+ ALOGE_IF(ret != 0, "APerformaceHint_setThreads failed: %d", ret);
+ return ret;
+ });
+}
+
void HintSessionWrapper::sendLoadResetHint() {
static constexpr int kMaxResetsSinceLastReport = 2;
if (!init()) return;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 41891cd..14e7a53 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -20,6 +20,7 @@
#include <future>
#include <optional>
+#include <vector>
#include "utils/TimeUtils.h"
@@ -47,11 +48,15 @@
nsecs_t getLastUpdate();
void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
std::shared_ptr<HintSessionWrapper> wrapperPtr);
+ // Must be called on Render thread. Otherwise can cause a race condition.
+ void setActiveFunctorThreads(std::vector<pid_t> threadIds);
private:
APerformanceHintSession* mHintSession = nullptr;
// This needs to work concurrently for testing
std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
+ // This needs to work concurrently for testing
+ std::optional<std::shared_future<int>> mSetThreadsFuture;
int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
@@ -59,6 +64,8 @@
pid_t mUiThreadId;
pid_t mRenderThreadId;
+ std::vector<pid_t> mPermanentSessionTids;
+ std::vector<pid_t> mActiveFunctorTids;
bool mSessionValid = true;
@@ -82,6 +89,8 @@
void (*reportActualWorkDuration)(APerformanceHintSession* session,
int64_t actualDuration) = nullptr;
void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr;
+ int (*setThreads)(APerformanceHintSession* session, const pid_t* tids,
+ size_t size) = nullptr;
private:
bool mInitialized = false;
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 10a740a1..c16602c 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -58,6 +58,7 @@
MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+ MOCK_METHOD(int, fakeSetThreads, (APerformanceHintSession*, const std::vector<pid_t>&));
// Needs to be on the binding so it can be accessed from static methods
std::promise<int> allowCreationToFinish;
};
@@ -102,11 +103,20 @@
static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
sMockBinding->fakeSendHint(session, hintId);
};
+ static int stubSetThreads(APerformanceHintSession* session, const pid_t* ids, size_t size) {
+ std::vector<pid_t> tids(ids, ids + size);
+ return sMockBinding->fakeSetThreads(session, tids);
+ }
void waitForWrapperReady() {
if (mWrapper->mHintSessionFuture.has_value()) {
mWrapper->mHintSessionFuture->wait();
}
}
+ void waitForSetThreadsReady() {
+ if (mWrapper->mSetThreadsFuture.has_value()) {
+ mWrapper->mSetThreadsFuture->wait();
+ }
+ }
void scheduleDelayedDestroyManaged() {
TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
// Guaranteed to be scheduled first, allows destruction to start
@@ -130,6 +140,7 @@
mWrapper->mBinding = sMockBinding;
EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr));
+ ON_CALL(*sMockBinding, fakeSetThreads).WillByDefault(Return(0));
}
void HintSessionWrapperTests::MockHintSessionBinding::init() {
@@ -141,6 +152,7 @@
sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
sMockBinding->sendHint = &stubSendHint;
+ sMockBinding->setThreads = &stubSetThreads;
}
void HintSessionWrapperTests::TearDown() {
@@ -339,4 +351,44 @@
EXPECT_EQ(mWrapper->alive(), false);
}
+TEST_F(HintSessionWrapperTests, setThreadsUpdatesSessionThreads) {
+ EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1);
+ EXPECT_CALL(*sMockBinding, fakeSetThreads(sessionPtr, testing::IsSupersetOf({11, 22})))
+ .Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+
+ // This changes the overall set of threads in the session, so the session wrapper should call
+ // setThreads.
+ mWrapper->setActiveFunctorThreads({11, 22});
+ waitForSetThreadsReady();
+
+ // The set of threads doesn't change, so the session wrapper should not call setThreads this
+ // time. The order of the threads shouldn't matter.
+ mWrapper->setActiveFunctorThreads({22, 11});
+ waitForSetThreadsReady();
+}
+
+TEST_F(HintSessionWrapperTests, setThreadsDoesntCrashAfterDestroy) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ // setActiveFunctorThreads shouldn't do anything, and shouldn't crash.
+ EXPECT_CALL(*sMockBinding, fakeSetThreads(_, _)).Times(0);
+ mWrapper->setActiveFunctorThreads({11, 22});
+ waitForSetThreadsReady();
+}
+
} // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 1905fa8..82d43bc 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -65,6 +65,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
/**
MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
@@ -2014,6 +2015,23 @@
}
}
+ // HACKY(b/325389296): aconfig flag accessors may not work in all contexts where MediaCodec API
+ // is used, so allow accessors to fail. In those contexts use a default value, normally false.
+
+ /* package private */
+ static boolean GetFlag(Supplier<Boolean> flagValueSupplier) {
+ return GetFlag(flagValueSupplier, false /* defaultValue */);
+ }
+
+ /* package private */
+ static boolean GetFlag(Supplier<Boolean> flagValueSupplier, boolean defaultValue) {
+ try {
+ return flagValueSupplier.get();
+ } catch (java.lang.RuntimeException e) {
+ return defaultValue;
+ }
+ }
+
private boolean mHasSurface = false;
/**
@@ -2346,7 +2364,7 @@
}
// at the moment no codecs support detachable surface
- if (android.media.codec.Flags.nullOutputSurface()) {
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
// Detached surface flag is only meaningful if surface is null. Otherwise, it is
// ignored.
if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index abad460..8ff4305 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.MediaCodec.GetFlag;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -827,10 +828,10 @@
features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false));
features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
features.add(new Feature(FEATURE_LowLatency, (1 << 7), true));
- if (android.media.codec.Flags.dynamicColorAspects()) {
+ if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
}
- if (android.media.codec.Flags.nullOutputSurface()) {
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));
}
@@ -851,10 +852,10 @@
features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
- if (android.media.codec.Flags.hlgEditing()) {
+ if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
}
- if (android.media.codec.Flags.regionOfInterest()) {
+ if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
features.add(new Feature(FEATURE_Roi, (1 << 7), true));
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index e89becd..fc96896 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -16,6 +16,9 @@
package com.android.mediaframeworktest.integration;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
+
import android.hardware.CameraInfo;
import android.hardware.ICamera;
import android.hardware.ICameraClient;
@@ -75,8 +78,8 @@
@SmallTest
public void testNumberOfCameras() throws Exception {
-
- int numCameras = mUtils.getCameraService().getNumberOfCameras(CAMERA_TYPE_ALL);
+ int numCameras = mUtils.getCameraService().getNumberOfCameras(CAMERA_TYPE_ALL,
+ DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(),
numCameras >= mUtils.getGuessedNumCameras());
Log.v(TAG, "Number of cameras " + numCameras);
@@ -85,9 +88,8 @@
@SmallTest
public void testCameraInfo() throws Exception {
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
-
CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
- /*overrideToPortrait*/false);
+ /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
assertTrue("Orientation was not set for camera " + cameraId,
info.info.orientation != -1);
@@ -163,7 +165,8 @@
ICameraService.USE_CALLING_PID,
getContext().getApplicationInfo().targetSdkVersion,
/*overrideToPortrait*/false,
- /*forceSlowJpegMode*/false);
+ /*forceSlowJpegMode*/false,
+ DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -184,7 +187,6 @@
public void onDeviceError(int errorCode, CaptureResultExtras resultExtras)
throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -197,7 +199,6 @@
public void onCaptureStarted(CaptureResultExtras resultExtras, long timestamp)
throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -211,7 +212,6 @@
public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -221,7 +221,6 @@
@Override
public void onDeviceIdle() throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -231,7 +230,6 @@
@Override
public void onPrepared(int streamId) throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -241,7 +239,6 @@
@Override
public void onRequestQueueEmpty() throws RemoteException {
// TODO Auto-generated method stub
-
}
/*
@@ -269,7 +266,7 @@
clientPackageName, clientAttributionTag,
ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false);
+ /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -280,18 +277,18 @@
static class DummyCameraServiceListener extends ICameraServiceListener.Stub {
@Override
- public void onStatusChanged(int status, String cameraId)
+ public void onStatusChanged(int status, String cameraId, int deviceId)
throws RemoteException {
Log.v(TAG, String.format("Camera %s has status changed to 0x%x", cameraId, status));
}
- public void onTorchStatusChanged(int status, String cameraId)
+ public void onTorchStatusChanged(int status, String cameraId, int deviceId)
throws RemoteException {
Log.v(TAG, String.format("Camera %s has torch status changed to 0x%x",
cameraId, status));
}
@Override
public void onPhysicalCameraStatusChanged(int status, String cameraId,
- String physicalCameraId) throws RemoteException {
+ String physicalCameraId, int deviceId) throws RemoteException {
Log.v(TAG, String.format("Camera %s : %s has status changed to 0x%x",
cameraId, physicalCameraId, status));
}
@@ -300,16 +297,16 @@
Log.v(TAG, "Camera access permission change");
}
@Override
- public void onCameraOpened(String cameraId, String clientPackageName) {
+ public void onCameraOpened(String cameraId, String clientPackageName, int deviceId) {
Log.v(TAG, String.format("Camera %s is opened by client package %s",
cameraId, clientPackageName));
}
@Override
- public void onCameraClosed(String cameraId) {
+ public void onCameraClosed(String cameraId, int deviceId) {
Log.v(TAG, String.format("Camera %s is closed", cameraId));
}
@Override
- public void onTorchStrengthLevelChanged(String cameraId, int torchStrength) {
+ public void onTorchStrengthLevelChanged(String cameraId, int torchStrength, int deviceId) {
Log.v(TAG, String.format("Camera " + cameraId + " torch strength level changed to "
+ torchStrength ));
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index eaa5a85..dc8647f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -16,6 +16,8 @@
package com.android.mediaframeworktest.integration;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW;
import static org.mockito.Mockito.any;
@@ -246,7 +248,7 @@
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
/*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
- /*overrideToPortrait*/false);
+ /*overrideToPortrait*/false, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
@@ -272,7 +274,6 @@
metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
assertFalse(metadata.isEmpty());
-
}
@SmallTest
@@ -306,7 +307,6 @@
@SmallTest
public void testCreateStreamTwo() throws Exception {
-
// Create first stream
int streamId = mCameraUser.createStream(mOutputConfiguration);
assertEquals(0, streamId);
@@ -335,7 +335,6 @@
@SmallTest
public void testSubmitBadRequest() throws Exception {
-
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
CaptureRequest request1 = builder.build();
try {
@@ -361,7 +360,6 @@
@SmallTest
public void testSubmitGoodRequest() throws Exception {
-
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
CaptureRequest request = builder.build();
@@ -370,12 +368,10 @@
SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
assertNotSame("Request IDs should be unique for multiple requests",
requestInfo1.getRequestId(), requestInfo2.getRequestId());
-
}
@SmallTest
public void testSubmitStreamingRequest() throws Exception {
-
CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
CaptureRequest request = builder.build();
@@ -419,7 +415,8 @@
@SmallTest
public void testCameraCharacteristics() throws RemoteException {
CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
- getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false);
+ getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false,
+ DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
assertFalse(info.isEmpty());
assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
@@ -512,7 +509,6 @@
// And wait for more idle
verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle();
-
}
@SmallTest
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 3fb91522..7bb08d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -17,6 +17,8 @@
package com.android.credentialmanager.common.ui
import android.content.Context
+import android.util.Log
+import com.android.credentialmanager.common.Constants
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -29,7 +31,8 @@
private const val SET_ADJUST_VIEW_BOUNDS_METHOD_NAME = "setAdjustViewBounds"
private const val SET_MAX_HEIGHT_METHOD_NAME = "setMaxHeight"
private const val SET_BACKGROUND_RESOURCE_METHOD_NAME = "setBackgroundResource"
- private const val BULLET_POINT = "\u2022"
+ private const val SEPARATOR = " " + "\u2022" + " "
+
// TODO(jbabs): RemoteViews#setViewPadding renders this as 8dp on the display. Debug why.
private const val END_ITEMS_PADDING = 28
@@ -43,20 +46,14 @@
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_presentation_layout
val remoteViews = RemoteViews(context.packageName, layoutId)
- if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
- return remoteViews
- }
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
- val secondaryText =
- if (credentialEntryInfo.displayName != null
- && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
- (credentialEntryInfo.userName + " " + BULLET_POINT + " "
- + credentialEntryInfo.credentialTypeDisplayName
- + " " + BULLET_POINT + " " + credentialEntryInfo.providerDisplayName)
- else (credentialEntryInfo.credentialTypeDisplayName + " " + BULLET_POINT + " "
- + credentialEntryInfo.providerDisplayName)
- remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ val secondaryText = getSecondaryText(credentialEntryInfo)
+ if (secondaryText.isNullOrBlank()) {
+ Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null")
+ } else {
+ remoteViews.setTextViewText(android.R.id.text2, secondaryText)
+ }
remoteViews.setImageViewIcon(android.R.id.icon1, icon);
remoteViews.setBoolean(
android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true);
@@ -88,6 +85,26 @@
return remoteViews
}
+ /**
+ * Computes the secondary text for dropdown presentation based on available fields.
+ *
+ * <p> Format for secondary text is [username] . [credentialType] . [providerDisplayName]
+ * If display name and username are the same, we do not display username
+ * If credential type is missing as in the case with SiwG, we just display
+ * providerDisplayName. Both credential type and provider display name should not be empty.
+ */
+ private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? {
+ return listOf(if (credentialEntryInfo.displayName != null
+ && (credentialEntryInfo.displayName != credentialEntryInfo.userName))
+ (credentialEntryInfo.userName) else null,
+ credentialEntryInfo.credentialTypeDisplayName,
+ credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() }
+ .let { itemsToDisplay ->
+ if (itemsToDisplay.isEmpty()) null
+ else itemsToDisplay.joinToString(separator = SEPARATOR)
+ }
+ }
+
fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_bottom_sheet
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 976a3ad..bcc737a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -30,6 +30,8 @@
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -46,6 +48,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -135,10 +138,10 @@
static final class AppSnippet implements Parcelable {
@NonNull public CharSequence label;
- @Nullable public Drawable icon;
+ @NonNull public Drawable icon;
public int iconSize;
- AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) {
+ AppSnippet(@NonNull CharSequence label, @NonNull Drawable icon, Context context) {
this.label = label;
this.icon = icon;
final ActivityManager am = context.getSystemService(ActivityManager.class);
@@ -147,14 +150,15 @@
private AppSnippet(Parcel in) {
label = in.readString();
- Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
+ byte[] b = in.readBlob();
+ Bitmap bmp = BitmapFactory.decodeByteArray(b, 0, b.length);
icon = new BitmapDrawable(Resources.getSystem(), bmp);
iconSize = in.readInt();
}
@Override
public String toString() {
- return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
+ return "AppSnippet[" + label + " (has icon)]";
}
@Override
@@ -165,16 +169,18 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(label.toString());
+
Bitmap bmp = getBitmapFromDrawable(icon);
- dest.writeParcelable(bmp, 0);
+ dest.writeBlob(getBytesFromBitmap(bmp));
+ bmp.recycle();
+
dest.writeInt(iconSize);
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
// Create an empty bitmap with the dimensions of our drawable
final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
// Associate it with a canvas. This canvas will draw the icon on the bitmap
final Canvas canvas = new Canvas(bmp);
// Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
@@ -192,6 +198,23 @@
return bmp;
}
+ private byte[] getBytesFromBitmap(Bitmap bmp) {
+ ByteArrayOutputStream baos = null;
+ try {
+ baos = new ByteArrayOutputStream();
+ bmp.compress(CompressFormat.PNG, 100, baos);
+ } finally {
+ try {
+ if (baos != null) {
+ baos.close();
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "ByteArrayOutputStream was not closed");
+ }
+ }
+ return baos.toByteArray();
+ }
+
public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
public AppSnippet createFromParcel(Parcel in) {
return new AppSnippet(in);
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
new file mode 100644
index 0000000..c7fbb5f
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
new file mode 100644
index 0000000..a2b9648
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:ellipsize="marquee" />
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@+id/settingslib_learn_more"
+ android:text="@string/settingslib_learn_more_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingBottom="8dp"
+ android:clickable="true"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
new file mode 100644
index 0000000..fff41c3
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="TextAppearance.TopIntroText"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
index d72ec26..69aba71 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -33,10 +33,6 @@
val secondaryText: Color = Color.Unspecified,
val primaryContainer: Color = Color.Unspecified,
val onPrimaryContainer: Color = Color.Unspecified,
- val spinnerHeaderContainer: Color = Color.Unspecified,
- val onSpinnerHeaderContainer: Color = Color.Unspecified,
- val spinnerItemContainer: Color = Color.Unspecified,
- val onSpinnerItemContainer: Color = Color.Unspecified,
)
internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
@@ -76,10 +72,6 @@
secondaryText = tonalPalette.neutralVariant30,
primaryContainer = tonalPalette.primary90,
onPrimaryContainer = tonalPalette.neutral10,
- spinnerHeaderContainer = tonalPalette.primary90,
- onSpinnerHeaderContainer = tonalPalette.neutral10,
- spinnerItemContainer = tonalPalette.secondary90,
- onSpinnerItemContainer = tonalPalette.neutralVariant30,
)
}
@@ -103,10 +95,6 @@
secondaryText = tonalPalette.neutralVariant80,
primaryContainer = tonalPalette.secondary90,
onPrimaryContainer = tonalPalette.neutral10,
- spinnerHeaderContainer = tonalPalette.primary90,
- onSpinnerHeaderContainer = tonalPalette.neutral10,
- spinnerItemContainer = tonalPalette.secondary90,
- onSpinnerItemContainer = tonalPalette.neutralVariant30,
)
}
@@ -121,10 +109,6 @@
secondaryText = tonalPalette.neutralVariant80,
primaryContainer = tonalPalette.secondary90,
onPrimaryContainer = tonalPalette.neutral10,
- spinnerHeaderContainer = tonalPalette.primary90,
- onSpinnerHeaderContainer = tonalPalette.neutral10,
- spinnerItemContainer = tonalPalette.secondary90,
- onSpinnerItemContainer = tonalPalette.neutralVariant30,
)
}
@@ -139,9 +123,5 @@
secondaryText = tonalPalette.neutralVariant30,
primaryContainer = tonalPalette.primary90,
onPrimaryContainer = tonalPalette.neutral10,
- spinnerHeaderContainer = tonalPalette.primary90,
- onSpinnerHeaderContainer = tonalPalette.neutral10,
- spinnerItemContainer = tonalPalette.secondary90,
- onSpinnerItemContainer = tonalPalette.neutralVariant30,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index 514ad669..c48a147 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -33,6 +33,7 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
@@ -74,8 +75,8 @@
modifier = Modifier.semantics { role = Role.DropdownList },
onClick = { expanded = true },
colors = ButtonDefaults.buttonColors(
- containerColor = SettingsTheme.colorScheme.spinnerHeaderContainer,
- contentColor = SettingsTheme.colorScheme.onSpinnerHeaderContainer,
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
),
contentPadding = contentPadding,
) {
@@ -85,7 +86,7 @@
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
- modifier = Modifier.background(SettingsTheme.colorScheme.spinnerItemContainer),
+ modifier = Modifier.background(MaterialTheme.colorScheme.secondaryContainer),
) {
for (option in options) {
DropdownMenuItem(
@@ -93,7 +94,7 @@
SpinnerText(
option = option,
modifier = Modifier.padding(end = 24.dp),
- color = SettingsTheme.colorScheme.onSpinnerItemContainer,
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
)
},
onClick = {
@@ -138,7 +139,7 @@
@Composable
private fun SpinnerPreview() {
SettingsTheme {
- var selectedId by rememberSaveable { mutableStateOf(1) }
+ var selectedId by rememberSaveable { mutableIntStateOf(1) }
Spinner(
options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
selectedId = selectedId,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
index 5ea92ab..625663d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt
@@ -37,15 +37,11 @@
assertThat(ls.categoryTitle).isNotEqualTo(ls.background)
assertThat(ls.secondaryText).isNotEqualTo(ls.background)
assertThat(ls.primaryContainer).isNotEqualTo(ls.onPrimaryContainer)
- assertThat(ls.spinnerHeaderContainer).isNotEqualTo(ls.onSpinnerHeaderContainer)
- assertThat(ls.spinnerItemContainer).isNotEqualTo(ls.onSpinnerItemContainer)
val ds = dynamicDarkColorScheme(context)
assertThat(ds.categoryTitle).isNotEqualTo(ds.background)
assertThat(ds.secondaryText).isNotEqualTo(ds.background)
assertThat(ds.primaryContainer).isNotEqualTo(ds.onPrimaryContainer)
- assertThat(ds.spinnerHeaderContainer).isNotEqualTo(ds.onSpinnerHeaderContainer)
- assertThat(ds.spinnerItemContainer).isNotEqualTo(ds.onSpinnerItemContainer)
}
@Test
@@ -58,10 +54,6 @@
assertThat(ls.secondaryText).isEqualTo(Color(red = 73, green = 69, blue = 79))
assertThat(ls.primaryContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
assertThat(ls.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
- assertThat(ls.spinnerHeaderContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
- assertThat(ls.onSpinnerHeaderContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
- assertThat(ls.spinnerItemContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
- assertThat(ls.onSpinnerItemContainer).isEqualTo(Color(red = 73, green = 69, blue = 79))
val ds = darkColorScheme()
assertThat(ds.background).isEqualTo(Color(red = 28, green = 27, blue = 31))
@@ -71,9 +63,5 @@
assertThat(ds.secondaryText).isEqualTo(Color(red = 202, green = 196, blue = 208))
assertThat(ds.primaryContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
assertThat(ds.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
- assertThat(ds.spinnerHeaderContainer).isEqualTo(Color(red = 234, green = 221, blue = 255))
- assertThat(ds.onSpinnerHeaderContainer).isEqualTo(Color(red = 28, green = 27, blue = 31))
- assertThat(ds.spinnerItemContainer).isEqualTo(Color(red = 232, green = 222, blue = 248))
- assertThat(ds.onSpinnerItemContainer).isEqualTo(Color(red = 73, green = 69, blue = 79))
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8a5dfef..46c6494 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -222,6 +222,30 @@
<!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aid_left_and_right_active">Active, left and right</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_active_media_only_battery_level">Active (media only), <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level for untethered headset. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_active_media_only_battery_level_untethered">Active (media only), L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_battery_level_lea_support">Connected (supports audio sharing), <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_battery_level_untethered_lea_support">Connected (supports audio sharing), L: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery, R: <xliff:g id="battery_level_as_percentage" example="25%">%2$s</xliff:g> battery</string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the left part of the untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_battery_level_untethered_left_lea_support">Connected (supports audio sharing), left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
+ <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset, supports audio sharing. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_battery_level_untethered_right_lea_support">Connected (supports audio sharing), right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string>
+ <!-- Connected devices settings. Message when Bluetooth is connected and active for media only but no battery information, showing remote device status. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_active_media_only_no_battery_level">Active (media only)</string>
+ <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device, supports audio sharing [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_saved_device_lea_support">Supports audio sharing</string>
+
+ <!-- Connected device settings. Message when the left-side hearing aid device is active for media only. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_hearing_aid_media_only_left_active">Active (media only), left only</string>
+ <!-- Connected device settings. Message when the right-side hearing aid device is active for media only. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_hearing_aid_media_only_right_active">Active (media only), right only</string>
+ <!-- Connected device settings. Message when the left-side and right-side hearing aids device are active for media only. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_hearing_aid_media_only_left_and_right_active">Active (media only), left and right</string>
+
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
<string name="bluetooth_profile_a2dp">Media audio</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. -->
@@ -1030,13 +1054,6 @@
<!-- Settings item title to select whether to disable cache for transcoding. [CHAR LIMIT=85] -->
<string name="transcode_disable_cache">Disable transcoding cache</string>
- <!-- Developer settings title: widevine settings screen. [CHAR LIMIT=50] -->
- <string name="widevine_settings_title">Widevine settings</string>
- <!-- Developer settings title: select whether to enable Force L3 fallback. [CHAR LIMIT=50] -->
- <string name="force_l3_fallback_title">Force L3 fallback</string>
- <!-- Developer settings summary: select whether to enable Force L3 fallback.[CHAR LIMIT=NONE] -->
- <string name="force_l3_fallback_summary">Select to force L3 fallback</string>
-
<!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
<string name="runningservices_settings_title">Running services</string>
<!-- Services settings screen, setting option summary for the user to go to the screen to view running services -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2889ce2..56118da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -51,9 +51,11 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.Formatter;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
@@ -990,11 +992,22 @@
apps = new ArrayList<>(mAppEntries);
}
+ ArrayMap<UserHandle, Boolean> profileHideInQuietModeStatus = new ArrayMap<>();
ArrayList<AppEntry> filteredApps = new ArrayList<>();
if (DEBUG) {
Log.i(TAG, "Rebuilding...");
}
for (AppEntry entry : apps) {
+ if (android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
+ UserHandle userHandle = UserHandle.of(UserHandle.getUserId(entry.info.uid));
+ if (!profileHideInQuietModeStatus.containsKey(userHandle)) {
+ profileHideInQuietModeStatus.put(
+ userHandle, isHideInQuietEnabledForProfile(mUm, userHandle));
+ }
+ filter.refreshAppEntryOnRebuild(
+ entry, profileHideInQuietModeStatus.get(userHandle));
+ }
if (entry != null && (filter == null || filter.filterApp(entry))) {
synchronized (mEntriesMap) {
if (DEBUG_LOCKING) {
@@ -1648,6 +1661,11 @@
*/
public boolean isHomeApp;
+ /**
+ * Whether the app should be hidden for user when quiet mode is enabled.
+ */
+ public boolean hideInQuietMode;
+
public String getNormalizedLabel() {
if (normalizedLabel != null) {
return normalizedLabel;
@@ -1691,6 +1709,7 @@
UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
mProfileType = userInfo.userType;
this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+ hideInQuietMode = shouldHideInQuietMode(um, info.uid);
}
public boolean isClonedProfile() {
@@ -1800,12 +1819,32 @@
this.labelDescription = this.label;
}
}
+
+ /**
+ * Returns true if profile is in quiet mode and the profile should not be visible when the
+ * quiet mode is enabled, false otherwise.
+ */
+ private boolean shouldHideInQuietMode(@NonNull UserManager userManager, int uid) {
+ if (android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
+ UserHandle userHandle = UserHandle.of(UserHandle.getUserId(uid));
+ return isHideInQuietEnabledForProfile(userManager, userHandle);
+ }
+ return false;
+ }
}
private static boolean hasFlag(int flags, int flag) {
return (flags & flag) != 0;
}
+ private static boolean isHideInQuietEnabledForProfile(
+ UserManager userManager, UserHandle userHandle) {
+ return userManager.isQuietModeEnabled(userHandle)
+ && userManager.getUserProperties(userHandle).getShowInQuietMode()
+ == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN;
+ }
+
/**
* Compare by label, then package name, then uid.
*/
@@ -1868,6 +1907,15 @@
}
boolean filterApp(AppEntry info);
+
+ /**
+ * Updates AppEntry based on whether quiet mode is enabled and should not be
+ * visible for the corresponding profile.
+ */
+ default void refreshAppEntryOnRebuild(
+ @NonNull AppEntry appEntry,
+ boolean hideInQuietMode) {
+ }
}
public static final AppFilter FILTER_PERSONAL = new AppFilter() {
@@ -2010,6 +2058,25 @@
}
};
+ public static final AppFilter FILTER_ENABLED_NOT_QUIET = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(@NonNull AppEntry entry) {
+ return entry.info.enabled && !AppUtils.isInstant(entry.info)
+ && !entry.hideInQuietMode;
+ }
+
+ @Override
+ public void refreshAppEntryOnRebuild(
+ @NonNull AppEntry appEntry,
+ boolean hideInQuietMode) {
+ appEntry.hideInQuietMode = hideInQuietMode;
+ }
+ };
+
public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
@Override
public void init() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index e7fec69..65a5317 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -73,6 +73,10 @@
suspend fun setVolume(audioStream: AudioStream, volume: Int)
suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+
+ suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
+
+ suspend fun isAffectedByMute(audioStream: AudioStream): Boolean
}
class AudioRepositoryImpl(
@@ -169,6 +173,16 @@
)
}
+ override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
+ withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
+ }
+
+ override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean {
+ return withContext(backgroundCoroutineContext) {
+ audioManager.isStreamAffectedByMute(audioStream.value)
+ }
+ }
+
private fun getMinVolume(stream: AudioStream): Int =
try {
audioManager.getStreamMinVolume(stream.value)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 778653b..33f917e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -49,8 +49,14 @@
suspend fun setVolume(audioStream: AudioStream, volume: Int) =
audioRepository.setVolume(audioStream, volume)
- suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ if (audioStream.value == AudioManager.STREAM_RING) {
+ val mode =
+ if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
+ audioRepository.setRingerMode(audioStream, RingerMode(mode))
+ }
audioRepository.setMuted(audioStream, isMuted)
+ }
/** Checks if the volume can be changed via the UI. */
fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
@@ -66,9 +72,8 @@
}
}
- fun isMutable(audioStream: AudioStream): Boolean =
- // Alarm stream doesn't support muting
- audioStream.value != AudioManager.STREAM_ALARM
+ suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
+ audioRepository.isAffectedByMute(audioStream)
private suspend fun processVolume(
audioStreamModel: AudioStreamModel,
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index fe83ffb..b974888 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -267,6 +267,16 @@
}
@Test
+ public void testEnabledFilterNotQuietRejectsInstantApp() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_HANDLE_INTERLEAVED_SETTINGS_FOR_PRIVATE_SPACE);
+ mEntry.info.enabled = true;
+ assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isTrue();
+ when(mEntry.info.isInstantApp()).thenReturn(true);
+ assertThat(ApplicationsState.FILTER_ENABLED_NOT_QUIET.filterApp(mEntry)).isFalse();
+ }
+
+ @Test
public void testFilterWithDomainUrls() {
mEntry.info.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
// should included updated system apps
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index b151a53..4e4c22f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -600,14 +600,25 @@
}
Setting oldState = mSettings.get(name);
+ String previousOwningPackage = (oldState != null) ? oldState.packageName : null;
+ // If the old state doesn't exist, no need to handle the owning package change
+ final boolean owningPackageChanged = previousOwningPackage != null
+ && !previousOwningPackage.equals(packageName);
+
String oldValue = (oldState != null) ? oldState.value : null;
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
String newDefaultValue = makeDefault ? value : oldDefaultValue;
- int newSize = getNewMemoryUsagePerPackageLocked(packageName,
- oldValue == null ? name.length() : 0 /* deltaKeySize */,
- oldValue, value, oldDefaultValue, newDefaultValue);
- checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+ int newSizeForCurrentPackage = getNewMemoryUsagePerPackageLocked(packageName,
+ /* deltaKeyLength= */ (oldState == null || owningPackageChanged) ? name.length() : 0,
+ /* oldValue= */ owningPackageChanged ? null : oldValue,
+ /* newValue= */ value,
+ /* oldDefaultValue= */ owningPackageChanged ? null : oldDefaultValue,
+ /* newDefaultValue = */ newDefaultValue);
+ // Only check the memory usage for the current package. Even if the owning package
+ // has changed, the previous owning package will only have a reduced memory usage, so
+ // there is no need to check its memory usage.
+ checkNewMemoryUsagePerPackageLocked(packageName, newSizeForCurrentPackage);
Setting newState;
@@ -629,7 +640,17 @@
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, newSize);
+ updateMemoryUsagePerPackageLocked(packageName, newSizeForCurrentPackage);
+
+ if (owningPackageChanged) {
+ int newSizeForPreviousPackage = getNewMemoryUsagePerPackageLocked(previousOwningPackage,
+ /* deltaKeyLength= */ -name.length(),
+ /* oldValue= */ oldValue,
+ /* newValue= */ null,
+ /* oldDefaultValue= */ oldDefaultValue,
+ /* newDefaultValue = */ null);
+ updateMemoryUsagePerPackageLocked(previousOwningPackage, newSizeForPreviousPackage);
+ }
scheduleWriteIfNeededLocked();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index e0e31d7..5db97c6 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -585,9 +585,9 @@
* Character.BYTES;
assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
- // Test system package
+ // Let system package take over testKey1 which is no longer subject to memory usage counting
settingsState.insertSettingLocked(testKey1, testValue1, null, true, SYSTEM_PACKAGE);
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
assertEquals(0, settingsState.getMemoryUsage(SYSTEM_PACKAGE));
@@ -599,7 +599,7 @@
} catch (IllegalStateException ex) {
assertTrue(ex.getMessage().contains("You are adding too many system settings"));
}
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
// Test invalid key
try {
@@ -609,7 +609,7 @@
} catch (IllegalStateException ex) {
assertTrue(ex.getMessage().contains("You are adding too many system settings"));
}
- assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
+ assertEquals(0, settingsState.getMemoryUsage(TEST_PACKAGE));
}
@Test
@@ -902,4 +902,49 @@
assertEquals("false", s.getValue());
}
}
+
+ @Test
+ public void testMemoryUsagePerPackage_SameSettingUsedByDifferentPackages() {
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ final String testKey1 = SETTING_NAME;
+ final String testKey2 = SETTING_NAME + "_2";
+ final String testValue1 = Strings.repeat("A", 100);
+ final String testValue2 = Strings.repeat("A", 50);
+ final String package1 = "p1";
+ final String package2 = "p2";
+
+ settingsState.insertSettingLocked(testKey1, testValue1, null, false, package1);
+ settingsState.insertSettingLocked(testKey2, testValue1, null, true, package2);
+ // Package1's usage should be remain the same Package2 owns a different setting
+ int expectedMemUsageForPackage1 = (testKey1.length() + testValue1.length())
+ * Character.BYTES;
+ int expectedMemUsageForPackage2 = (testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+
+ settingsState.insertSettingLocked(testKey1, testValue2, null, false, package2);
+ // Package1's usage should be cleared because the setting is taken over by another package
+ expectedMemUsageForPackage1 = 0;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ // Package2 now owns two settings
+ expectedMemUsageForPackage2 = (testKey1.length() + testValue2.length()
+ + testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */)
+ * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+
+ settingsState.insertSettingLocked(testKey1, testValue1, null, true, package1);
+ // Package1 now owns setting1
+ expectedMemUsageForPackage1 = (testKey1.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage1, settingsState.getMemoryUsage(package1));
+ // Package2 now only own setting2
+ expectedMemUsageForPackage2 = (testKey2.length() + testValue1.length()
+ + testValue1.length() /* size for default */) * Character.BYTES;
+ assertEquals(expectedMemUsageForPackage2, settingsState.getMemoryUsage(package2));
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f911269..dc7850f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -289,7 +289,7 @@
"WindowManager-Shell",
"LowLightDreamLib",
"motion_tool_lib",
- "androidx.core_core-animation-testing-nodeps",
+ "androidx.core_core-animation-testing",
"androidx.compose.ui_ui",
"flag-junit",
"ravenwood-junit",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index f70ad9e..5f3d1ea 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -62,6 +62,7 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -452,6 +453,8 @@
}
@Test
+ @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
+ + "Coverage is low-impact.")
public void testOnScreenLock_cannotOpenMenu() throws Throwable {
closeScreen();
wakeUpScreen();
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 2268d16..a6d750f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -41,7 +41,7 @@
],
static_libs: [
- "androidx.core_core-animation-nodeps",
+ "androidx.core_core-animation",
"androidx.core_core-ktx",
"androidx.annotation_annotation",
"com_android_systemui_flags_lib",
@@ -64,7 +64,7 @@
],
static_libs: [
- "androidx.core_core-animation-nodeps",
+ "androidx.core_core-animation",
"androidx.core_core-ktx",
"androidx.annotation_annotation",
],
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index dbdf970..24cc8a4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -711,7 +711,6 @@
dialog.setDismissOverride(this::onDialogDismissed)
if (featureFlags.isPredictiveBackQsDialogAnim) {
- // TODO(b/265923095) Improve animations for QS dialogs on configuration change
dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index dd32851..4d327e1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -37,7 +37,7 @@
/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */
fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec(
- displayMetrics: DisplayMetrics,
+ displayMetricsProvider: () -> DisplayMetrics,
maxMarginXdp: Float,
maxMarginYdp: Float,
minScale: Float,
@@ -45,18 +45,19 @@
translateYEasing: Interpolator = Interpolators.LINEAR,
scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
): BackAnimationSpec {
- val screenWidthPx = displayMetrics.widthPixels
- val screenHeightPx = displayMetrics.heightPixels
-
- val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
- val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
- val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
- val maxTranslationX = maxTranslationXByScale - maxMarginXPx
- val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
- val maxTranslationY = maxTranslationYByScale - maxMarginYPx
- val minScaleReversed = 1f - minScale
-
return BackAnimationSpec { backEvent, progressY, result ->
+ val displayMetrics = displayMetricsProvider()
+ val screenWidthPx = displayMetrics.widthPixels
+ val screenHeightPx = displayMetrics.heightPixels
+
+ val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
+ val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
+ val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
+ val maxTranslationX = maxTranslationXByScale - maxMarginXPx
+ val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
+ val maxTranslationY = maxTranslationYByScale - maxMarginYPx
+ val minScaleReversed = 1f - minScale
+
val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1
val progressX = backEvent.progress
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
index c6b7073..b057296 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
@@ -23,10 +23,10 @@
* https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app
*/
fun BackAnimationSpec.Companion.dismissAppForSysUi(
- displayMetrics: DisplayMetrics,
+ displayMetricsProvider: () -> DisplayMetrics,
): BackAnimationSpec =
BackAnimationSpec.createFloatingSurfaceAnimationSpec(
- displayMetrics = displayMetrics,
+ displayMetricsProvider = displayMetricsProvider,
maxMarginXdp = 8f,
maxMarginYdp = 8f,
minScale = 0.8f,
@@ -37,10 +37,10 @@
* https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task
*/
fun BackAnimationSpec.Companion.crossTaskForSysUi(
- displayMetrics: DisplayMetrics,
+ displayMetricsProvider: () -> DisplayMetrics,
): BackAnimationSpec =
BackAnimationSpec.createFloatingSurfaceAnimationSpec(
- displayMetrics = displayMetrics,
+ displayMetricsProvider = displayMetricsProvider,
maxMarginXdp = 8f,
maxMarginYdp = 8f,
minScale = 0.8f,
@@ -51,10 +51,10 @@
* https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss
*/
fun BackAnimationSpec.Companion.innerAreaDismissForSysUi(
- displayMetrics: DisplayMetrics,
+ displayMetricsProvider: () -> DisplayMetrics,
): BackAnimationSpec =
BackAnimationSpec.createFloatingSurfaceAnimationSpec(
- displayMetrics = displayMetrics,
+ displayMetricsProvider = displayMetricsProvider,
maxMarginXdp = 0f,
maxMarginYdp = 0f,
minScale = 0.9f,
@@ -65,11 +65,11 @@
* https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces
*/
fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi(
- displayMetrics: DisplayMetrics,
+ displayMetricsProvider: () -> DisplayMetrics,
): BackAnimationSpec =
BackAnimationSpec.createFloatingSurfaceAnimationSpec(
- displayMetrics = displayMetrics,
+ displayMetricsProvider = displayMetricsProvider,
maxMarginXdp = 8f,
maxMarginYdp = 8f,
- minScale = 0.8f,
+ minScale = 0.9f,
)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 0f63548..9dd2328 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -39,7 +39,7 @@
targetView: View,
backAnimationSpec: BackAnimationSpec =
BackAnimationSpec.floatingSystemSurfacesForSysUi(
- displayMetrics = targetView.resources.displayMetrics,
+ displayMetricsProvider = { targetView.resources.displayMetrics },
),
) {
targetView.registerOnBackInvokedCallbackOnViewAttached(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a1d8c29..bdd888f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -8,12 +8,15 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.FixedSizeEdgeDetector
import com.android.compose.animation.scene.LowestZIndexScenePicker
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -21,12 +24,13 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.compose.animation.scene.updateSceneTransitionLayoutState
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
+import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
object Communal {
@@ -63,27 +67,35 @@
fun CommunalContainer(
modifier: Modifier = Modifier,
viewModel: CommunalViewModel,
+ dataSourceDelegator: SceneDataSourceDelegator,
dialogFactory: SystemUIDialogFactory,
) {
- val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
- val sceneTransitionLayoutState =
- updateSceneTransitionLayoutState(
- currentScene,
- onChangeScene = { viewModel.onSceneChanged(it) },
+ val coroutineScope = rememberCoroutineScope()
+ val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+ val state: MutableSceneTransitionLayoutState = remember {
+ MutableSceneTransitionLayoutState(
+ initialScene = currentSceneKey,
transitions = sceneTransitions,
enableInterruptions = false,
)
- val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false)
+ }
+
+ DisposableEffect(state) {
+ val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
+ dataSourceDelegator.setDelegate(dataSource)
+ onDispose { dataSourceDelegator.setDelegate(null) }
+ }
// This effect exposes the SceneTransitionLayout's observable transition state to the rest of
// the system, and unsets it when the view is disposed to avoid a memory leak.
- DisposableEffect(viewModel, sceneTransitionLayoutState) {
- viewModel.setTransitionState(sceneTransitionLayoutState.observableTransitionState())
+ DisposableEffect(viewModel, state) {
+ viewModel.setTransitionState(state.observableTransitionState())
onDispose { viewModel.setTransitionState(null) }
}
SceneTransitionLayout(
- state = sceneTransitionLayoutState,
+ state = state,
modifier = modifier.fillMaxSize(),
swipeSourceDetector =
FixedSizeEdgeDetector(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e1608c4..ed80277 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -110,6 +110,8 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -154,6 +156,7 @@
derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
var isButtonToEditWidgetsShowing by remember { mutableStateOf(false) }
+ val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false)
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -178,7 +181,7 @@
viewModel.setSelectedKey(key)
}
}
- .thenIf(!viewModel.isEditMode) {
+ .thenIf(!viewModel.isEditMode && !isEmptyState) {
Modifier.pointerInput(
gridState,
contentOffset,
@@ -219,26 +222,33 @@
.motionEventSpy { onMotionEvent(viewModel) }
},
) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
+ if (!viewModel.isEditMode && isEmptyState) {
+ EmptyStateCta(
+ contentPadding = contentPadding,
+ viewModel = viewModel,
+ )
+ } else {
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
+ }
// TODO(b/326060686): Remove this once keyguard indication area can persist over hub
if (viewModel is CommunalViewModel) {
@@ -460,6 +470,67 @@
}
}
+/**
+ * The empty state displays a fullscreen call-to-action (CTA) tile when no widgets are available.
+ */
+@Composable
+private fun EmptyStateCta(
+ contentPadding: PaddingValues,
+ viewModel: BaseCommunalViewModel,
+) {
+ val colors = LocalAndroidColorScheme.current
+ Card(
+ modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, colors.secondary),
+ shape = RoundedCornerShape(size = 80.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(horizontal = 110.dp),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = stringResource(R.string.title_for_empty_state_cta),
+ style = MaterialTheme.typography.displaySmall,
+ textAlign = TextAlign.Center,
+ color = colors.secondary,
+ )
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ Button(
+ modifier = Modifier.height(56.dp),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
+ ),
+ onClick = {
+ viewModel.onOpenWidgetEditor(
+ shouldOpenWidgetPickerOnStart = true,
+ )
+ },
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription =
+ stringResource(R.string.label_for_button_in_empty_state_cta),
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(Modifier.width(ButtonDefaults.IconSpacing))
+ Text(
+ text = stringResource(R.string.label_for_button_in_empty_state_cta),
+ style = MaterialTheme.typography.titleSmall,
+ )
+ }
+ }
+ }
+ }
+}
+
@Composable
private fun LockStateIcon(
isUnlocked: Boolean,
@@ -514,7 +585,7 @@
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
+ val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing)
Button(
onClick = onOpenWidgetPicker,
colors = filledButtonColors(),
@@ -669,8 +740,6 @@
is CommunalContentModel.WidgetContent.DisabledWidget ->
DisabledWidgetPlaceholder(model, viewModel, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
- is CommunalContentModel.CtaTileInEditMode ->
- CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -756,45 +825,6 @@
}
}
-/** Presents a CTA tile at the end of the hub in edit mode, to add more widgets. */
-@Composable
-private fun CtaTileInEditModeContent(
- modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
-) {
- if (onOpenWidgetPicker == null) {
- throw IllegalArgumentException("onOpenWidgetPicker should not be null.")
- }
- val colors = LocalAndroidColorScheme.current
- Card(
- modifier = modifier,
- colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(1.dp, colors.primary),
- shape = RoundedCornerShape(200.dp),
- onClick = onOpenWidgetPicker,
- ) {
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement =
- Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Icon(
- imageVector = Icons.Outlined.Widgets,
- contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
- tint = colors.primary,
- modifier = Modifier.size(Dimensions.IconSize),
- )
- Text(
- text = stringResource(R.string.cta_label_to_open_widget_picker),
- style = MaterialTheme.typography.titleLarge,
- color = colors.primary,
- textAlign = TextAlign.Center,
- )
- }
- }
-}
-
@Composable
private fun WidgetContent(
viewModel: BaseCommunalViewModel,
@@ -1045,7 +1075,6 @@
val ToolbarPaddingHorizontal = 16.dp
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
- val ToolbarButtonSpaceBetween = 8.dp
val ButtonPadding =
PaddingValues(
vertical = ToolbarButtonPaddingVertical,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 1adb335..e7bfee3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -34,10 +34,12 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
@@ -235,6 +237,7 @@
}
/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
+@OptIn(ExperimentalComposeUiApi::class)
@ExperimentalFoundationApi
@Composable
fun LazyGridItemScope.DraggableItem(
@@ -269,7 +272,11 @@
Box(modifier) {
Box(draggingModifier) { content(dragging) }
AnimatedVisibility(
- modifier = Modifier.matchParentSize(),
+ modifier =
+ Modifier.matchParentSize()
+ // Do not consume motion events in the highlighted item and pass them down to
+ // the content.
+ .pointerInteropFilter { false },
visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
enter = fadeIn(),
exit = fadeOut()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 1178cc8..4bef9ef 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.composable
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
@@ -24,9 +23,7 @@
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneTransitionLayout
-import com.android.compose.animation.scene.transitions
+import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -45,16 +42,12 @@
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
private val clockInteractor: KeyguardClockInteractor,
) {
-
- private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
- blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
- }
- private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
- sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey }
+ private val blueprintByBlueprintId: Map<String, ComposableLockscreenSceneBlueprint> by lazy {
+ blueprints.associateBy { it.id }
}
@Composable
- fun Content(
+ fun SceneScope.Content(
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
@@ -66,19 +59,7 @@
onDispose { clockInteractor.clockEventController.unregisterListeners() }
}
- // Switch smoothly between blueprints, any composable tagged with element() will be
- // transition-animated between any two blueprints, if they both display the same element.
- SceneTransitionLayout(
- currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]),
- onChangeScene = {},
- transitions =
- transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } },
- modifier = modifier,
- enableInterruptions = false,
- ) {
- sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) ->
- scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } }
- }
- }
+ val blueprint = blueprintByBlueprintId[blueprintId] ?: return
+ with(blueprint) { Content(modifier) }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7acb4d5..c241f9c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -66,9 +66,5 @@
key = QuickSettings.SharedValues.TilesSquishiness,
)
- lockscreenContent
- .get()
- .Content(
- modifier = modifier.fillMaxSize(),
- )
+ with(lockscreenContent.get()) { Content(modifier = modifier.fillMaxSize()) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 320c455..c008a1a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -16,10 +16,15 @@
package com.android.systemui.keyguard.ui.composable.blueprint
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
@@ -28,6 +33,7 @@
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
@@ -52,6 +58,7 @@
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val topAreaSection: TopAreaSection,
+ private val notificationSection: NotificationSection,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "default"
@@ -59,6 +66,8 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
+ val shouldUseSplitNotificationShade by
+ viewModel.shouldUseSplitNotificationShade.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -68,10 +77,27 @@
content = {
// Constrained to above the lock icon.
Column(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.fillMaxSize(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(topAreaSection) { DefaultClockLayoutWithNotifications() }
+
+ Box {
+ with(topAreaSection) { DefaultClockLayout() }
+ if (shouldUseSplitNotificationShade) {
+ with(notificationSection) {
+ Notifications(
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
+ )
+ }
+ }
+ }
+ if (!shouldUseSplitNotificationShade) {
+ with(notificationSection) {
+ Notifications(Modifier.weight(weight = 1f))
+ }
+ }
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
with(ambientIndicationSectionOptional.get()) {
AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 64c2cb3..091a439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -16,10 +16,15 @@
package com.android.systemui.keyguard.ui.composable.blueprint
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
@@ -28,6 +33,7 @@
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
@@ -52,6 +58,7 @@
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val topAreaSection: TopAreaSection,
+ private val notificationSection: NotificationSection,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -59,6 +66,8 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
+ val shouldUseSplitNotificationShade by
+ viewModel.shouldUseSplitNotificationShade.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -68,11 +77,27 @@
content = {
// Constrained to above the lock icon.
Column(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.fillMaxSize(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(topAreaSection) { DefaultClockLayoutWithNotifications() }
+ Box {
+ with(topAreaSection) { DefaultClockLayout() }
+ if (shouldUseSplitNotificationShade) {
+ with(notificationSection) {
+ Notifications(
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
+ )
+ }
+ }
+ }
+ if (!shouldUseSplitNotificationShade) {
+ with(notificationSection) {
+ Notifications(Modifier.weight(weight = 1f))
+ }
+ }
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
with(ambientIndicationSectionOptional.get()) {
AmbientIndication(modifier = Modifier.fillMaxWidth())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index fe774a0..09d76a3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -86,6 +86,7 @@
val burnIn = rememberBurnIn(clockInteractor)
val resources = LocalContext.current.resources
val currentClockState = clockViewModel.currentClock.collectAsState()
+ val areNotificationsVisible by viewModel.areNotificationsVisible.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
modifier = modifier,
@@ -145,7 +146,7 @@
with(mediaCarouselSection) { MediaCarousel() }
- if (viewModel.areNotificationsVisible) {
+ if (areNotificationsVisible) {
with(notificationSection) {
Notifications(
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6b86a48..fa0a1c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -17,12 +17,25 @@
package com.android.systemui.keyguard.ui.composable.section
import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
@@ -39,6 +52,7 @@
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
+ private val lockscreenContentViewModel: LockscreenContentViewModel,
) {
init {
@@ -65,9 +79,27 @@
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ val shouldUseSplitNotificationShade by
+ lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
+ val areNotificationsVisible by
+ lockscreenContentViewModel.areNotificationsVisible.collectAsState()
+ val splitShadeTopMargin: Dp =
+ if (Flags.centralizedStatusBarHeightFix()) {
+ LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
+ } else {
+ dimensionResource(id = R.dimen.large_screen_shade_header_height)
+ }
+
+ if (!areNotificationsVisible) {
+ return
+ }
+
NotificationStack(
viewModel = viewModel,
- modifier = modifier,
+ modifier =
+ modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
+ Modifier.padding(top = splitShadeTopMargin)
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index b4472fc..f8e6341 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -16,30 +16,20 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.thenIf
-import com.android.systemui.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
@@ -48,8 +38,6 @@
import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.shade.LargeScreenHeaderHelper
import javax.inject.Inject
class TopAreaSection
@@ -58,19 +46,16 @@
private val clockViewModel: KeyguardClockViewModel,
private val smartSpaceSection: SmartSpaceSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val notificationSection: NotificationSection,
private val clockSection: DefaultClockSection,
private val clockInteractor: KeyguardClockInteractor,
) {
@Composable
- fun DefaultClockLayoutWithNotifications(
+ fun DefaultClockLayout(
modifier: Modifier = Modifier,
) {
- val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
val hasCustomPositionUpdatedAnimation by
clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState()
-
val currentScene =
when (currentClockLayout) {
KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
@@ -81,144 +66,83 @@
KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene
}
- val splitShadeTopMargin: Dp =
- if (Flags.centralizedStatusBarHeightFix()) {
- LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
- } else {
- dimensionResource(id = R.dimen.large_screen_shade_header_height)
+ SceneTransitionLayout(
+ modifier = modifier,
+ currentScene = currentScene,
+ onChangeScene = {},
+ transitions = ClockTransition.defaultClockTransitions,
+ enableInterruptions = false,
+ ) {
+ scene(splitShadeLargeClockScene) {
+ LargeClockWithSmartSpace(
+ shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
+ )
}
+
+ scene(splitShadeSmallClockScene) {
+ SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ }
+
+ scene(smallClockScene) { SmallClockWithSmartSpace() }
+
+ scene(largeClockScene) { LargeClockWithSmartSpace() }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) {
val burnIn = rememberBurnIn(clockInteractor)
+ Column(modifier = modifier) {
+ with(clockSection) {
+ SmallClock(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.wrapContentSize()
+ )
+ }
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ )
+ }
+ with(mediaCarouselSection) { MediaCarousel() }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
+ val burnIn = rememberBurnIn(clockInteractor)
+ val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
+
LaunchedEffect(isLargeClockVisible) {
if (isLargeClockVisible) {
burnIn.onSmallClockTopChanged(null)
}
}
- SceneTransitionLayout(
- modifier = modifier.fillMaxSize(),
- currentScene = currentScene,
- onChangeScene = {},
- transitions = ClockTransition.defaultClockTransitions,
- enableInterruptions = false,
- ) {
- scene(splitShadeLargeClockScene) {
- Box(modifier = Modifier.fillMaxSize()) {
- Column(
- modifier = Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- )
- }
-
- with(clockSection) {
- LargeClock(
- modifier =
- Modifier.fillMaxSize().thenIf(
- !hasCustomPositionUpdatedAnimation
- ) {
- // If we do not have a custom position animation, we want
- // the clock to be on one half of the screen.
- Modifier.offset {
- IntOffset(
- x =
- -clockSection
- .getClockCenteringDistance()
- .toInt(),
- y = 0,
- )
- }
- }
- )
- }
- }
- }
-
- Row(
- modifier = Modifier.fillMaxSize(),
- ) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(notificationSection) {
- Notifications(
- modifier =
- Modifier.fillMaxHeight()
- .weight(weight = 1f)
- .padding(top = splitShadeTopMargin)
- )
- }
- }
+ Column {
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ )
}
-
- scene(splitShadeSmallClockScene) {
- Row(
- modifier = Modifier.fillMaxSize(),
- ) {
- Column(
- modifier = Modifier.fillMaxHeight().weight(weight = 1f),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.wrapContentSize()
- )
+ with(clockSection) {
+ LargeClock(
+ modifier =
+ Modifier.fillMaxSize().thenIf(shouldOffSetClockToOneHalf) {
+ // If we do not have a custom position animation, we want
+ // the clock to be on one half of the screen.
+ Modifier.offset {
+ IntOffset(
+ x = -clockSection.getClockCenteringDistance().toInt(),
+ y = 0,
+ )
+ }
}
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- )
- }
- with(mediaCarouselSection) { MediaCarousel() }
- }
- with(notificationSection) {
- Notifications(
- modifier =
- Modifier.fillMaxHeight()
- .weight(weight = 1f)
- .padding(top = splitShadeTopMargin)
- )
- }
- }
- }
-
- scene(smallClockScene) {
- Column {
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.wrapContentSize()
- )
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- )
- }
- with(mediaCarouselSection) { MediaCarousel() }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(weight = 1f))
- }
- }
- }
-
- scene(largeClockScene) {
- Column {
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- )
- }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxSize()) }
- }
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index c5c48e6..fcd7760 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -114,6 +114,11 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val isDisabled by viewModel.isDisabled.collectAsState()
+ if (isDisabled) {
+ return
+ }
+
val formatProgress =
animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
.unsafeCompositionState(initialValue = 0f)
@@ -251,6 +256,11 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val isDisabled by viewModel.isDisabled.collectAsState()
+ if (isDisabled) {
+ return
+ }
+
val formatProgress =
animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
.unsafeCompositionState(initialValue = 1f)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 25c649a..003c572 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -27,7 +27,9 @@
import android.text.format.DateFormat
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
+import android.util.TypedValue
import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
@@ -42,6 +44,7 @@
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
+import kotlin.math.min
/**
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
@@ -85,6 +88,8 @@
private var textAnimator: TextAnimator? = null
private var onTextAnimatorInitialized: Runnable? = null
+ // last text size which is not constrained by view height
+ private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
{ layout, invalidateCb ->
TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
@@ -185,9 +190,22 @@
refreshFormat()
}
+ override fun setTextSize(type: Int, size: Float) {
+ super.setTextSize(type, size)
+ if (type == TypedValue.COMPLEX_UNIT_PX) {
+ lastUnconstrainedTextSize = size
+ }
+ }
+
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure")
+ if (migratedClocks && !isSingleLineInternal &&
+ MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+ // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
+ super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 324534f..7986051 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -105,7 +105,7 @@
when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
when(mView.getUnpausedAlpha()).thenReturn(255);
when(mShadeExpansionStateManager.addExpansionListener(any())).thenReturn(
- new ShadeExpansionChangeEvent(0, false, false, 0));
+ new ShadeExpansionChangeEvent(0, false, false));
mController = createUdfpsKeyguardViewController();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 43266bf..a944afb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -88,7 +88,7 @@
testScope.runTest {
val scene by collectLastValue(communalInteractor.desiredScene)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -158,7 +158,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalInteractor.desiredScene)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -179,7 +179,7 @@
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalInteractor.desiredScene)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -207,7 +207,7 @@
fun dockingOnLockscreen_forcesCommunal() =
with(kosmos) {
testScope.runTest {
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
val scene by collectLastValue(communalInteractor.desiredScene)
// device is docked while on the lockscreen
@@ -229,7 +229,7 @@
fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
with(kosmos) {
testScope.runTest {
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
val scene by collectLastValue(communalInteractor.desiredScene)
// device is docked while on the lockscreen
@@ -261,7 +261,7 @@
testScope.runTest {
// Device is dreaming and on communal.
fakeKeyguardRepository.setDreaming(true)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -278,7 +278,7 @@
testScope.runTest {
// Device is not dreaming and on communal.
fakeKeyguardRepository.setDreaming(false)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
// Scene stays as Communal
advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
@@ -293,7 +293,7 @@
testScope.runTest {
// Device is dreaming and on communal.
fakeKeyguardRepository.setDreaming(true)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -316,7 +316,7 @@
testScope.runTest {
// Device is on communal, but not dreaming.
fakeKeyguardRepository.setDreaming(false)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -338,7 +338,7 @@
testScope.runTest {
// Device is dreaming and on communal.
fakeKeyguardRepository.setDreaming(true)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -367,7 +367,7 @@
// Device is dreaming and on communal.
fakeKeyguardRepository.setDreaming(true)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
val scene by collectLastValue(communalInteractor.desiredScene)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 43acf31..2d78a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -22,36 +22,26 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalRepositoryImplTest : SysuiTestCase() {
- private lateinit var underTest: CommunalRepositoryImpl
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneContainerRepository = kosmos.sceneContainerRepository
-
- @Before
- fun setUp() {
- underTest = createRepositoryImpl(false)
- }
-
- private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
- return CommunalRepositoryImpl(
- testScope.backgroundScope,
- kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
- sceneContainerRepository,
+ private val underTest by lazy {
+ CommunalRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ kosmos.sceneDataSource,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index a5707e1..e7ccde2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -482,7 +482,7 @@
assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank)
val targetScene = CommunalScenes.Communal
- communalRepository.setDesiredScene(targetScene)
+ communalRepository.changeScene(targetScene)
desiredScene = collectLastValue(underTest.desiredScene)
runCurrent()
assertThat(desiredScene()).isEqualTo(targetScene)
@@ -493,9 +493,9 @@
testScope.runTest {
val targetScene = CommunalScenes.Communal
- underTest.onSceneChanged(targetScene)
+ underTest.changeScene(targetScene)
- val desiredScene = collectLastValue(communalRepository.desiredScene)
+ val desiredScene = collectLastValue(communalRepository.currentScene)
runCurrent()
assertThat(desiredScene()).isEqualTo(targetScene)
}
@@ -508,7 +508,7 @@
val desiredScene by collectLastValue(underTest.desiredScene)
- underTest.onSceneChanged(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal)
assertThat(desiredScene).isEqualTo(CommunalScenes.Communal)
kosmos.setCommunalAvailable(false)
@@ -659,7 +659,7 @@
runCurrent()
assertThat(isCommunalShowing()).isEqualTo(false)
- underTest.onSceneChanged(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal)
isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
runCurrent()
@@ -683,12 +683,12 @@
assertThat(isCommunalShowing).isFalse()
// Verify scene changes (without the flag) to communal sets the value to true
- underTest.onSceneChanged(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal)
runCurrent()
assertThat(isCommunalShowing).isTrue()
// Verify scene changes (without the flag) to blank sets the value back to false
- underTest.onSceneChanged(CommunalScenes.Blank)
+ underTest.changeScene(CommunalScenes.Blank)
runCurrent()
assertThat(isCommunalShowing).isFalse()
}
@@ -704,7 +704,7 @@
assertThat(isCommunalShowing).isFalse()
// Verify scene changes without the flag doesn't have any impact
- underTest.onSceneChanged(CommunalScenes.Communal)
+ underTest.changeScene(CommunalScenes.Communal)
runCurrent()
assertThat(isCommunalShowing).isFalse()
@@ -817,6 +817,13 @@
}
@Test
+ fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() =
+ testScope.runTest {
+ underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true)
+ verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true)
+ }
+
+ @Test
fun navigateToCommunalWidgetSettings_startsActivity() =
testScope.runTest {
underTest.navigateToCommunalWidgetSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 50b8da6..3a23e14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -158,7 +158,7 @@
kosmos.setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
}
@@ -171,7 +171,7 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -184,13 +184,13 @@
goToCommunal()
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
private suspend fun goToCommunal() {
kosmos.setCommunalAvailable(true)
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index a7e98ea..9aebc30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -149,13 +149,11 @@
val communalContent by collectLastValue(underTest.communalContent)
// Only Widgets and CTA tile are shown.
- assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.size).isEqualTo(2)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
- assertThat(communalContent?.get(2))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@Test
@@ -195,24 +193,20 @@
val communalContent by collectLastValue(underTest.communalContent)
// Widgets and CTA tile are shown.
- assertThat(communalContent?.size).isEqualTo(3)
+ assertThat(communalContent?.size).isEqualTo(2)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
- assertThat(communalContent?.get(2))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
underTest.onDeleteWidget(widgets.get(0).appWidgetId)
// Only one widget and CTA tile remain.
- assertThat(communalContent?.size).isEqualTo(2)
+ assertThat(communalContent?.size).isEqualTo(1)
val item = communalContent?.get(0)
val appWidgetId =
if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
- assertThat(communalContent?.get(1))
- .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b8..5be765d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -194,6 +194,41 @@
}
@Test
+ fun isEmptyState_isTrue_noWidgetButActiveLiveContent() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ widgetRepository.setCommunalWidgets(emptyList())
+ // UMO playing
+ mediaRepository.mediaActive()
+ smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+
+ val isEmptyState by collectLastValue(underTest.isEmptyState)
+ assertThat(isEmptyState).isTrue()
+ }
+
+ @Test
+ fun isEmptyState_isFalse_withWidgets() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ widgetRepository.setCommunalWidgets(
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ priority = 1,
+ providerInfo = providerInfo,
+ )
+ ),
+ )
+ mediaRepository.mediaInactive()
+ smartspaceRepository.setCommunalSmartspaceTargets(emptyList())
+
+ val isEmptyState by collectLastValue(underTest.isEmptyState)
+ assertThat(isEmptyState).isFalse()
+ }
+
+ @Test
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 5827671..6a86801 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -376,7 +376,7 @@
// Ensure correct expansion passed in.
ShadeExpansionChangeEvent event =
new ShadeExpansionChangeEvent(
- expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
+ expansion, /* expanded= */ false, /* tracking= */ true);
verify(mScrimController).expand(event);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
index 97052a8..7cdd478 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -59,7 +59,7 @@
final BouncerlessScrimController scrimController =
new BouncerlessScrimController(mExecutor, mPowerManager);
scrimController.addCallback(mCallback);
- scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false, 0.0f));
+ scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false));
mExecutor.runAllReady();
verify(mPowerManager).wakeUp(anyLong(), eq(PowerManager.WAKE_REASON_GESTURE), any());
verify(mCallback).onWakeup();
@@ -71,7 +71,7 @@
new BouncerlessScrimController(mExecutor, mPowerManager);
scrimController.addCallback(mCallback);
final ShadeExpansionChangeEvent expansionEvent =
- new ShadeExpansionChangeEvent(0.5f, false, false, 0.0f);
+ new ShadeExpansionChangeEvent(0.5f, false, false);
scrimController.expand(expansionEvent);
mExecutor.runAllReady();
verify(mCallback).onExpansion(eq(expansionEvent));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 8a77ed2..056a401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -30,17 +30,24 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -49,22 +56,33 @@
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
-import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.keyguardOcclusionInteractor
- private val powerInteractor = kosmos.powerInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ private lateinit var underTest: KeyguardOcclusionInteractor
+ private lateinit var powerInteractor: PowerInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ @Before
+ fun setUp() {
+ powerInteractor = kosmos.powerInteractor
+ transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ underTest = kosmos.keyguardOcclusionInteractor
+ }
@Test
- fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+ fun transitionFromPowerGesture_whileGoingToSleep_isTrue() =
testScope.runTest {
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
@@ -81,7 +99,7 @@
}
@Test
- fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+ fun transitionFromPowerGesture_whileAsleep_isTrue() =
testScope.runTest {
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
@@ -97,7 +115,7 @@
}
@Test
- fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+ fun transitionFromPowerGesture_whileWaking_isFalse() =
testScope.runTest {
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
@@ -119,7 +137,7 @@
}
@Test
- fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+ fun transitionFromPowerGesture_whileAwake_isFalse() =
testScope.runTest {
powerInteractor.setAwakeForTest()
transitionRepository.sendTransitionSteps(
@@ -140,7 +158,7 @@
}
@Test
- fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+ fun showWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
testScope.runTest {
val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
powerInteractor.setAsleepForTest()
@@ -187,7 +205,7 @@
}
@Test
- fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+ fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
testScope.runTest {
val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
powerInteractor.setAwakeForTest()
@@ -221,4 +239,23 @@
false,
)
}
+
+ @Test
+ @EnableSceneContainer
+ fun occludingActivityWillDismissKeyguard() =
+ testScope.runTest {
+ val occludingActivityWillDismissKeyguard by
+ collectLastValue(underTest.occludingActivityWillDismissKeyguard)
+ assertThat(occludingActivityWillDismissKeyguard).isFalse()
+
+ // Unlock device:
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ assertThat(occludingActivityWillDismissKeyguard).isTrue()
+
+ // Re-lock device:
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ assertThat(occludingActivityWillDismissKeyguard).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 36458ed..15c9cf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -37,6 +38,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -242,7 +244,8 @@
@Test
fun transitionValue() =
testScope.runTest {
- val startedSteps by collectValues(underTest.transitionValue(state = DOZING))
+ resetTransitionValueReplayCache(setOf(AOD, DOZING, LOCKSCREEN))
+ val transitionValues by collectValues(underTest.transitionValue(state = DOZING))
val toSteps =
listOf(
@@ -266,12 +269,14 @@
runCurrent()
}
- assertThat(startedSteps).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
+ assertThat(transitionValues).isEqualTo(listOf(0f, 0.5f, 1f, 1f, 0.5f, 0f))
}
@Test
fun transitionValue_canceled_toAnotherState() =
testScope.runTest {
+ resetTransitionValueReplayCache(setOf(AOD, GONE, LOCKSCREEN))
+
val transitionValuesGone by collectValues(underTest.transitionValue(state = GONE))
val transitionValuesAod by collectValues(underTest.transitionValue(state = AOD))
val transitionValuesLs by collectValues(underTest.transitionValue(state = LOCKSCREEN))
@@ -297,6 +302,8 @@
@Test
fun transitionValue_canceled_backToOriginalState() =
testScope.runTest {
+ resetTransitionValueReplayCache(setOf(AOD, GONE))
+
val transitionValuesGone by collectValues(underTest.transitionValue(state = GONE))
val transitionValuesAod by collectValues(underTest.transitionValue(state = AOD))
@@ -1395,4 +1402,8 @@
testScope.runCurrent()
}
}
+
+ private fun resetTransitionValueReplayCache(states: Set<KeyguardState>) {
+ states.forEach { (underTest.transitionValue(it) as MutableSharedFlow).resetReplayCache() }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 0cc0c2f..78bdfb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,6 +52,7 @@
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val underTest by lazy { kosmos.alternateBouncerToGoneTransitionViewModel }
@Test
@@ -112,6 +114,21 @@
}
@Test
+ fun notificationAlpha_leaveShadeOpen() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha(ViewStateAccessor()))
+ runCurrent()
+
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ keyguardTransitionRepository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
fun lockscreenAlpha_zeroInitialAlpha() =
testScope.runTest {
// ViewState starts at 0 alpha.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index ad2ae8b..e6b3017 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -149,6 +150,38 @@
}
@Test
+ fun showAllNotifications_isTrue_whenLeaveShadeOpen() =
+ testScope.runTest {
+ val showAllNotifications by
+ collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER))
+
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ keyguardTransitionRepository.sendTransitionStep(step(0.1f))
+
+ assertThat(showAllNotifications).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(showAllNotifications).isFalse()
+ }
+
+ @Test
+ fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() =
+ testScope.runTest {
+ val showAllNotifications by
+ collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER))
+
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ keyguardTransitionRepository.sendTransitionStep(step(0.1f))
+
+ assertThat(showAllNotifications).isFalse()
+ keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+ assertThat(showAllNotifications).isFalse()
+ }
+
+ @Test
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 751ac1d..e9a8257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
@@ -96,7 +97,7 @@
shadeRepository.setShadeMode(ShadeMode.Split)
kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- assertThat(underTest.areNotificationsVisible).isTrue()
+ assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue()
}
}
@Test
@@ -104,7 +105,7 @@
with(kosmos) {
testScope.runTest {
kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
- assertThat(underTest.areNotificationsVisible).isTrue()
+ assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue()
}
}
@@ -113,7 +114,7 @@
with(kosmos) {
testScope.runTest {
kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- assertThat(underTest.areNotificationsVisible).isFalse()
+ assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isFalse()
}
}
@@ -122,7 +123,8 @@
with(kosmos) {
testScope.runTest {
shadeRepository.setShadeMode(ShadeMode.Split)
- assertThat(underTest.shouldUseSplitNotificationShade).isTrue()
+ assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke())
+ .isTrue()
}
}
@@ -131,16 +133,8 @@
with(kosmos) {
testScope.runTest {
shadeRepository.setShadeMode(ShadeMode.Single)
- assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
- }
- }
-
- @Test
- fun sceneKey() =
- with(kosmos) {
- testScope.runTest {
- shadeRepository.setShadeMode(ShadeMode.Single)
- assertThat(underTest.shouldUseSplitNotificationShade).isFalse()
+ assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke())
+ .isFalse()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
index 857b9f8..26cb485 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +41,7 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val repository = kosmos.fakeKeyguardTransitionRepository
+ private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val underTest = kosmos.lockscreenToGoneTransitionViewModel
@Test
@@ -90,6 +92,20 @@
assertThat(alpha).isEqualTo(0f)
}
+ @Test
+ fun notificationAlpha_leaveShadeOpen() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha(ViewStateAccessor()))
+
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 6108904..ef38567 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -97,6 +98,7 @@
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index af9abcd..efbdb7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -64,6 +64,7 @@
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -86,7 +87,6 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
@@ -242,6 +242,7 @@
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
@@ -286,6 +287,7 @@
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
centralSurfaces = mock(),
headsUpInteractor = kosmos.headsUpNotificationInteractor,
+ occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
)
startable.start()
@@ -549,21 +551,6 @@
assertCurrentScene(Scenes.Lockscreen)
}
- @Test
- fun deviceProvisioningAndFactoryResetProtection() =
- testScope.runTest {
- val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
- kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(false)
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
- assertThat(isVisible).isFalse()
-
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
- assertThat(isVisible).isFalse()
-
- kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
- assertThat(isVisible).isTrue()
- }
-
/**
* Asserts that the current scene in the view-model matches what's expected.
*
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
new file mode 100644
index 0000000..c3366ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneContainerOcclusionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardOcclusionInteractor = kosmos.keyguardOcclusionInteractor
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ private val mutableTransitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+ private val sceneInteractor =
+ kosmos.sceneInteractor.apply { setTransitionState(mutableTransitionState) }
+ private val sceneDataSource =
+ kosmos.sceneDataSource.apply { changeScene(toScene = Scenes.Lockscreen) }
+
+ private val underTest = kosmos.sceneContainerOcclusionInteractor
+
+ @Test
+ fun invisibleDueToOcclusion() =
+ testScope.runTest {
+ val invisibleDueToOcclusion by collectLastValue(underTest.invisibleDueToOcclusion)
+ val keyguardState by collectLastValue(keyguardTransitionInteractor.currentKeyguardState)
+
+ // Assert that we have the desired preconditions:
+ assertThat(keyguardState).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen)
+ assertThat(sceneInteractor.transitionState.value)
+ .isEqualTo(ObservableTransitionState.Idle(Scenes.Lockscreen))
+ assertWithMessage("Should start unoccluded").that(invisibleDueToOcclusion).isFalse()
+
+ // Actual testing starts here:
+ showOccludingActivity()
+ assertWithMessage("Should become occluded when occluding activity is shown")
+ .that(invisibleDueToOcclusion)
+ .isTrue()
+
+ transitionIntoAod {
+ assertWithMessage("Should become unoccluded when transitioning into AOD")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+ }
+ assertWithMessage("Should stay unoccluded when in AOD")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+
+ transitionOutOfAod {
+ assertWithMessage("Should remain unoccluded while transitioning away from AOD")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+ }
+ assertWithMessage("Should become occluded now that no longer in AOD")
+ .that(invisibleDueToOcclusion)
+ .isTrue()
+
+ expandShade {
+ assertWithMessage("Should become unoccluded once shade begins to expand")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+ }
+ assertWithMessage("Should be unoccluded when shade is fully expanded")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+
+ collapseShade {
+ assertWithMessage("Should remain unoccluded while shade is collapsing")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+ }
+ assertWithMessage("Should become occluded now that shade is fully collapsed")
+ .that(invisibleDueToOcclusion)
+ .isTrue()
+
+ hideOccludingActivity()
+ assertWithMessage("Should become unoccluded once the occluding activity is hidden")
+ .that(invisibleDueToOcclusion)
+ .isFalse()
+ }
+
+ /** Simulates the appearance of a show-when-locked `Activity` in the foreground. */
+ private fun TestScope.showOccludingActivity() {
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = true,
+ taskInfo = mock(),
+ )
+ runCurrent()
+ }
+
+ /** Simulates the disappearance of a show-when-locked `Activity` from the foreground. */
+ private fun TestScope.hideOccludingActivity() {
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = false,
+ )
+ runCurrent()
+ }
+
+ /** Simulates a user-driven gradual expansion of the shade. */
+ private fun TestScope.expandShade(
+ assertMidTransition: () -> Unit = {},
+ ) {
+ val progress = MutableStateFlow(0f)
+ mutableTransitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = sceneDataSource.currentScene.value,
+ toScene = Scenes.Shade,
+ progress = progress,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+
+ progress.value = 0.5f
+ runCurrent()
+ assertMidTransition()
+
+ progress.value = 1f
+ runCurrent()
+
+ mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
+ runCurrent()
+ }
+
+ /** Simulates a user-driven gradual collapse of the shade. */
+ private fun TestScope.collapseShade(
+ assertMidTransition: () -> Unit = {},
+ ) {
+ val progress = MutableStateFlow(0f)
+ mutableTransitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Shade,
+ toScene = Scenes.Lockscreen,
+ progress = progress,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+
+ progress.value = 0.5f
+ runCurrent()
+ assertMidTransition()
+
+ progress.value = 1f
+ runCurrent()
+
+ mutableTransitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+ runCurrent()
+ }
+
+ /** Simulates a transition into AOD. */
+ private suspend fun TestScope.transitionIntoAod(
+ assertMidTransition: () -> Unit = {},
+ ) {
+ val currentKeyguardState = keyguardTransitionInteractor.getCurrentState()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = currentKeyguardState,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = currentKeyguardState,
+ to = KeyguardState.AOD,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING,
+ )
+ )
+ runCurrent()
+ assertMidTransition()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = currentKeyguardState,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ runCurrent()
+ }
+
+ /** Simulates a transition away from AOD. */
+ private suspend fun TestScope.transitionOutOfAod(
+ assertMidTransition: () -> Unit = {},
+ ) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING,
+ )
+ )
+ runCurrent()
+ assertMidTransition()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index f018cc1..605e5c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -46,11 +46,13 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
@@ -128,6 +130,7 @@
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
centralSurfaces = centralSurfaces,
headsUpInteractor = kosmos.headsUpNotificationInteractor,
+ occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
)
}
@@ -187,27 +190,42 @@
}
@Test
- fun hydrateVisibility_basedOnDeviceProvisioningAndFactoryResetProtection() =
+ fun hydrateVisibility_basedOnDeviceProvisioning() =
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
prepareState(
isDeviceUnlocked = true,
initialSceneKey = Scenes.Lockscreen,
isDeviceProvisioned = false,
- isFrpActive = true,
)
underTest.start()
assertThat(isVisible).isFalse()
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(false)
- assertThat(isVisible).isFalse()
-
kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(true)
assertThat(isVisible).isTrue()
+ }
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ @Test
+ fun hydrateVisibility_basedOnOcclusion() =
+ testScope.runTest {
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
+ prepareState(
+ isDeviceUnlocked = true,
+ initialSceneKey = Scenes.Lockscreen,
+ )
+
+ underTest.start()
+ assertThat(isVisible).isTrue()
+
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ true,
+ mock()
+ )
assertThat(isVisible).isFalse()
+
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(false)
+ assertThat(isVisible).isTrue()
}
@Test
@@ -1030,7 +1048,6 @@
isLockscreenEnabled: Boolean = true,
startsAwake: Boolean = true,
isDeviceProvisioned: Boolean = true,
- isFrpActive: Boolean = false,
): MutableStateFlow<ObservableTransitionState> {
if (authenticationMethod?.isSecure == true) {
assert(isLockscreenEnabled) {
@@ -1068,7 +1085,6 @@
}
kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned)
- kosmos.fakeDeviceProvisioningRepository.setFactoryResetProtectionActive(isFrpActive)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index 16b68cc..ad40f8e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,6 +52,7 @@
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
private val sceneInteractor = kosmos.sceneInteractor
+ private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -112,6 +113,40 @@
changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
assertThat(panelExpansion).isEqualTo(1f)
}
+
+ @Test
+ @EnableSceneContainer
+ fun shouldHideStatusBarIconsWhenExpanded_goneScene() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractorImpl
+ shadeAnimationInteractor.setIsLaunchingActivity(false)
+ changeScene(Scenes.Gone)
+
+ assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun shouldHideStatusBarIconsWhenExpanded_lockscreenScene() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractorImpl
+ shadeAnimationInteractor.setIsLaunchingActivity(false)
+ changeScene(Scenes.Lockscreen)
+
+ assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun shouldHideStatusBarIconsWhenExpanded_activityLaunch() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractorImpl
+ changeScene(Scenes.Gone)
+ shadeAnimationInteractor.setIsLaunchingActivity(true)
+
+ assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
+ }
+
private fun TestScope.setUnlocked(isUnlocked: Boolean) {
val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
deviceEntryRepository.setUnlocked(isUnlocked)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 4e82feb..757a6c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -326,7 +326,7 @@
}
@Test
- fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+ fun shadeExpansionWhenNotInSplitShadeAndQsPartiallyExpanded() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
@@ -338,6 +338,22 @@
runCurrent()
// THEN shade expansion is zero
+ assertThat(actual).isEqualTo(.5f)
+ }
+
+ @Test
+ fun shadeExpansionWhenNotInSplitShadeAndQsFullyExpanded() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ // WHEN split shade is not enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeRepository.setQsExpansion(1f)
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN shade expansion is zero
assertThat(actual).isEqualTo(0f)
}
@@ -603,20 +619,6 @@
}
@Test
- fun isShadeTouchable_isFalse_whenFrpIsActive() =
- testScope.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- )
- )
- val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
- assertThat(isShadeTouchable).isFalse()
- }
-
- @Test
fun isShadeTouchable_isFalse_whenDeviceAsleepAndNotPulsing() =
testScope.runTest {
powerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
index 682c4ef..0ae95e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -95,14 +95,14 @@
}
@Test
- fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+ fun shadeExpansionWhenNotInSplitShadeAndQsFullyExpanded() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
// WHEN split shade is not enabled and QS is expanded
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
overrideResource(R.bool.config_use_split_notification_shade, false)
- shadeRepository.setQsExpansion(.5f)
+ shadeRepository.setQsExpansion(1f)
shadeRepository.setLegacyShadeExpansion(1f)
runCurrent()
@@ -111,16 +111,34 @@
}
@Test
+ fun shadeExpansionWhenNotInSplitShadeAndQsPartlyExpanded() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ // WHEN split shade is not enabled and QS partly expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeRepository.setQsExpansion(.4f)
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN shade expansion is the difference
+ assertThat(actual).isEqualTo(.6f)
+ }
+
+ @Test
fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
- // WHEN split shade is not enabled and QS is expanded
+ // WHEN split shade is not enabled and QS collapsed
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
shadeRepository.setQsExpansion(0f)
shadeRepository.setLegacyShadeExpansion(.6f)
+ runCurrent()
- // THEN shade expansion is zero
+ // THEN shade expansion is the legacy one
assertThat(actual).isEqualTo(.6f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 31dacdd..52caa78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -18,15 +18,32 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,11 +51,15 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeStartableTest : SysuiTestCase() {
-
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val shadeInteractor = kosmos.shadeInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val shadeExpansionStateManager = kosmos.shadeExpansionStateManager
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
private val fakeConfigurationRepository = kosmos.fakeConfigurationRepository
+ private val fakeSceneDataSource = kosmos.fakeSceneDataSource
private val underTest = kosmos.shadeStartable
@@ -59,4 +80,89 @@
fakeConfigurationRepository.onAnyConfigurationChange()
assertThat(shadeMode).isEqualTo(ShadeMode.Single)
}
+
+ @Test
+ @EnableSceneContainer
+ fun hydrateShadeExpansionStateManager() =
+ testScope.runTest {
+ val expansionListener = mock<ShadeExpansionListener>()
+ var latestChangeEvent: ShadeExpansionChangeEvent? = null
+ whenever(expansionListener.onPanelExpansionChanged(any())).thenAnswer {
+ latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
+ Unit
+ }
+ shadeExpansionStateManager.addExpansionListener(expansionListener)
+
+ underTest.start()
+
+ setUnlocked(true)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Gone)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ changeScene(Scenes.Gone, transitionState)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+
+ assertThat(latestChangeEvent)
+ .isEqualTo(
+ ShadeExpansionChangeEvent(
+ fraction = 0f,
+ expanded = false,
+ tracking = false,
+ )
+ )
+
+ changeScene(Scenes.Shade, transitionState) { progress ->
+ assertThat(latestChangeEvent?.fraction).isEqualTo(progress)
+ }
+ }
+
+ private fun TestScope.setUnlocked(isUnlocked: Boolean) {
+ val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
+ deviceEntryRepository.setUnlocked(isUnlocked)
+ runCurrent()
+
+ assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
+ }
+
+ private fun TestScope.changeScene(
+ toScene: SceneKey,
+ transitionState: MutableStateFlow<ObservableTransitionState>,
+ assertDuringProgress: ((progress: Float) -> Unit) = {},
+ ) {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = checkNotNull(currentScene),
+ toScene = toScene,
+ progress = progressFlow,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.2f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.6f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 1f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ transitionState.value = ObservableTransitionState.Idle(toScene)
+ fakeSceneDataSource.changeScene(toScene)
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ assertThat(currentScene).isEqualTo(toScene)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 062741d..4c573d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -13,6 +13,7 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -69,6 +70,7 @@
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index d1c4ec3..f90a3b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -110,6 +110,7 @@
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
+ shadeInteractor = kosmos.shadeInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = kosmos.privacyChipInteractor,
@@ -280,17 +281,17 @@
@Test
fun upTransitionSceneKey_customizing_noTransition() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- qsSceneAdapter.setCustomizing(true)
- assertThat(
- destinationScenes!!
- .keys
- .filterIsInstance<Swipe>()
- .filter { it.direction == SwipeDirection.Up }
- ).isEmpty()
- }
+ qsSceneAdapter.setCustomizing(true)
+ assertThat(
+ destinationScenes!!.keys.filterIsInstance<Swipe>().filter {
+ it.direction == SwipeDirection.Up
+ }
+ )
+ .isEmpty()
+ }
@Test
fun shadeMode() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 2a2b2f1..7ac549a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -483,6 +483,21 @@
}
@Test
+ fun shouldHideFooterView_falseWhenQSPartiallyOpen() =
+ testScope.runTest {
+ val shouldHide by collectLastValue(underTest.shouldHideFooterView)
+
+ // WHEN QS partially open
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ fakeShadeRepository.setQsExpansion(0.5f)
+ fakeShadeRepository.setLegacyShadeExpansion(0.5f)
+ runCurrent()
+
+ // THEN footer is hidden
+ assertThat(shouldHide).isFalse()
+ }
+
+ @Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
fun pinnedHeadsUpRows_filtersForPinnedItems() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index cc7ebe9..509a82d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -585,6 +585,12 @@
notificationCount = 25
sharedNotificationContainerInteractor.notificationStackChanged()
assertThat(maxNotifications).isEqualTo(25)
+
+ // Also ensure another collection starts with the same value. As an example, folding
+ // then unfolding will restart the coroutine and it must get the last value immediately.
+ val newMaxNotifications by
+ collectLastValue(underTest.getMaxNotifications(calculateSpace))
+ assertThat(newMaxNotifications).isEqualTo(25)
}
@Test
@@ -760,6 +766,41 @@
}
@Test
+ fun alphaDoesNotUpdateWhileOcclusionTransitionIsRunning() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreen()
+ // OCCLUDED transition gets to 90% complete
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.STARTED,
+ value = 0f,
+ )
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.RUNNING,
+ value = 0.9f,
+ )
+ )
+ runCurrent()
+
+ // At this point, alpha should be zero
+ assertThat(alpha).isEqualTo(0f)
+
+ // An attempt to override by the shade should be ignored
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
fun alphaWhenGoneIsSetToOne() =
testScope.runTest {
val viewState = ViewStateAccessor()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 781a9a8..7e5205b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LockIconViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthController;
@@ -93,7 +92,6 @@
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private ShadeLockscreenInteractor mShadeLockscreenInteractor;
- @Mock private LockIconViewController mLockIconViewController;
@Mock private View mAmbientIndicationContainer;
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 5358a6d..fa79e7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -201,11 +201,38 @@
}
@Test
- fun alarmStream_isNotMutable() {
+ fun streamNotAffectedByMute_isNotMutable() {
with(kosmos) {
- val isMutable = underTest.isMutable(AudioStream(AudioManager.STREAM_ALARM))
+ testScope.runTest {
+ audioRepository.setIsAffectedByMute(audioStream, false)
+ val isMutable = underTest.isAffectedByMute(audioStream)
- assertThat(isMutable).isFalse()
+ assertThat(isMutable).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun muteRingerStream_ringerMode_vibrate() {
+ with(kosmos) {
+ testScope.runTest {
+ val ringerMode by collectLastValue(audioRepository.ringerMode)
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+
+ assertThat(ringerMode).isEqualTo(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
+ }
+ }
+ }
+
+ @Test
+ fun unMuteRingerStream_ringerMode_normal() {
+ with(kosmos) {
+ testScope.runTest {
+ val ringerMode by collectLastValue(audioRepository.ringerMode)
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), false)
+
+ assertThat(ringerMode).isEqualTo(RingerMode(AudioManager.RINGER_MODE_NORMAL))
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 8e92557..2cc1ad3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -84,8 +84,17 @@
runCurrent()
- verify(activityStarter).startActivity(capture(intentCaptor), eq(true),
- capture(activityStartedCaptor))
+ verify(activityStarter)
+ .startActivityDismissingKeyguard(
+ /* intent = */ capture(intentCaptor),
+ /* onlyProvisioned = */ eq(false),
+ /* dismissShade = */ eq(true),
+ /* disallowEnterPictureInPictureWhileLaunching = */ eq(false),
+ /* callback = */ capture(activityStartedCaptor),
+ /* flags = */ eq(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
+ /* animationController = */ eq(null),
+ /* userHandle = */ eq(null),
+ )
assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index c99cb39..83658d3 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -129,6 +129,11 @@
void setDozeAmount(float amount);
/**
+ * Set if the screen is on.
+ */
+ default void setScreenOn(boolean screenOn) {}
+
+ /**
* Set if dozing is true or false
*/
default void setDozing(boolean dozing) {}
diff --git a/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
new file mode 100644
index 0000000..edfbe7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_fullscreen_magnification_border_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@*android:dimen/rounded_corner_radius" />
+ <!-- Since the device corners are not perfectly rounded, we create the stroke with offset
+ to fill up the space between border and device corner -->
+ <stroke
+ android:color="@color/magnification_border_color"
+ android:width="@dimen/magnifier_border_width_fullscreen_with_offset"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml
new file mode 100644
index 0000000..c1573a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hearing_devices_icon.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fullscreen_magnification_border.xml b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
new file mode 100644
index 0000000..5f738c0
--- /dev/null
+++ b/packages/SystemUI/res/layout/fullscreen_magnification_border.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnification_fullscreen_border"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false"
+ android:background="@drawable/accessibility_fullscreen_magnification_border_background"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index a64c9ae..9a7108a 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -19,6 +19,7 @@
android:id="@+id/global_actions_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:clipChildren="false"
android:gravity="center"
android:layout_gravity="center">
<com.android.systemui.globalactions.GlobalActionsLayoutLite
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index ef1a21f..c988b4a 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -28,7 +28,7 @@
android:elevation="4dp"
android:background="@drawable/action_chip_container_background"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/actions_container"
app:layout_constraintEnd_toEndOf="@+id/actions_container"
@@ -38,14 +38,14 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingEnd="@dimen/overlay_action_container_padding_end"
+ android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
android:elevation="4dp"
android:scrollbars="none"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintWidth_percent="1.0"
app:layout_constraintWidth_max="wrap"
- app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
<LinearLayout
@@ -65,16 +65,16 @@
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginStart="16dp"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
android:layout_marginTop="@dimen/overlay_border_width_neg"
android:layout_marginEnd="@dimen/overlay_border_width_neg"
- android:layout_marginBottom="14dp"
+ android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
android:elevation="8dp"
android:background="@drawable/overlay_border"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toBottomOf="parent"/>
+ app:layout_constraintBottom_toTopOf="@id/actions_container"/>
<ImageView
android:id="@+id/screenshot_preview"
android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 35f6a08..a6f6d4d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -101,7 +101,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e004ee9..d2efccd 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -448,6 +448,7 @@
<dimen name="overlay_action_container_padding_end">8dp</dimen>
<dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
<dimen name="overlay_dismiss_button_margin">8dp</dimen>
+ <dimen name="screenshot_shelf_vertical_margin">8dp</dimen>
<!-- must be kept aligned with overlay_border_width_neg, below;
overlay_border_width = overlay_border_width_neg * -1 -->
<dimen name="overlay_border_width">4dp</dimen>
@@ -1266,6 +1267,8 @@
<dimen name="magnifier_corner_radius">28dp</dimen>
<dimen name="magnifier_edit_corner_radius">16dp</dimen>
<dimen name="magnifier_edit_outer_corner_radius">18dp</dimen>
+ <dimen name="magnifier_border_width_fullscreen_with_offset">12dp</dimen>
+ <dimen name="magnifier_border_width_fullscreen">6dp</dimen>
<dimen name="magnifier_border_width">8dp</dimen>
<dimen name="magnifier_stroke_width">2dp</dimen>
<dimen name="magnifier_edit_dash_gap">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a9151e8..b0d98e7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -899,6 +899,9 @@
<!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
<string name="quick_settings_contrast_high">High</string>
+ <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_hearing_devices_label">Hearing devices</string>
+
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
<!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
@@ -1119,15 +1122,15 @@
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
- <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
+ <!-- Text for call-to-action button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
<string name="cta_tile_button_to_open_widget_editor">Customize</string>
- <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
+ <!-- Text for call-to-action button that dismisses the tile on click. [CHAR LIMIT=50] -->
<string name="cta_tile_button_to_dismiss">Dismiss</string>
- <!-- Label for CTA tile to edit the glanceable hub [CHAR LIMIT=100] -->
+ <!-- Label for call-to-action tile to edit the glanceable hub [CHAR LIMIT=100] -->
<string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
- <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
+ <!-- Label for call-to-action tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
- <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
+ <!-- Text for the popup to be displayed after dismissing the call-to-action tile. [CHAR LIMIT=50] -->
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
<!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
<string name="button_to_configure_widgets_text">Customize widgets</string>
@@ -1141,6 +1144,10 @@
<string name="hub_mode_add_widget_button_text">Add widget</string>
<!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
<string name="hub_mode_editing_exit_button_text">Done</string>
+ <!-- Label for the button in the empty state call-to-action tile that will open the widget picker. [CHAR LIMIT=NONE] -->
+ <string name="label_for_button_in_empty_state_cta">Add widgets</string>
+ <!-- Title for the empty state call-to-action when no widgets are available in the hub. [CHAR LIMIT=NONE] -->
+ <string name="title_for_empty_state_cta">Get quick access to your favorite app widgets without unlocking your tablet.</string>
<!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
<string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
<!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index 9036a35..ad09b46 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -338,4 +338,14 @@
<item>Off</item>
<item>On</item>
</string-array>
+
+ <!-- State names for hearing devices tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_hearing_devices">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 751a3f8..68d2eb3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -22,6 +22,7 @@
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
@@ -138,11 +139,15 @@
/** @return whether or not {@param context} represents that of a large screen device or not */
@TargetApi(Build.VERSION_CODES.R)
public static boolean isLargeScreen(Context context) {
- final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources());
+ }
+
+ /** @return whether or not {@param context} represents that of a large screen device or not */
+ public static boolean isLargeScreen(WindowManager windowManager, Resources resources) {
final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
- context.getResources().getConfiguration().densityDpi);
+ resources.getConfiguration().densityDpi);
return smallestWidth >= TABLET_MIN_DPS;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
new file mode 100644
index 0000000..b792db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.res.R
+import dagger.Lazy
+import javax.inject.Inject
+
+/**
+ * Lock icon view logic now lives in DeviceEntryIconViewBinder and ViewModels. Icon is positioned in
+ * [com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection].
+ *
+ * This class is to bridge the gap between the logic when the DeviceEntryUdfpsRefactor is enabled
+ * and the KeyguardBottomAreaRefactor is NOT enabled. This class can and should be removed when both
+ * flags are enabled.
+ */
+@SysUISingleton
+class EmptyLockIconViewController
+@Inject
+constructor(
+ private val keyguardRootView: Lazy<KeyguardRootView>,
+) : LockIconViewController {
+ private val deviceEntryIconViewId = R.id.device_entry_icon_view
+ override fun setLockIconView(lockIconView: LockIconView) {
+ // no-op
+ }
+
+ override fun getTop(): Float {
+ return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.top?.toFloat() ?: 0f
+ }
+
+ override fun getBottom(): Float {
+ return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.bottom?.toFloat() ?: 0f
+ }
+
+ override fun dozeTimeTick() {
+ // no-op
+ }
+
+ override fun setAlpha(alpha: Float) {
+ // no-op
+ }
+
+ override fun willHandleTouchWhileDozing(event: MotionEvent): Boolean {
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
similarity index 99%
rename from packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
rename to packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
index 8f1a5f7..4e5df35 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
@@ -98,7 +98,7 @@
* icon will show a set distance from the bottom of the device.
*/
@SysUISingleton
-public class LockIconViewController implements Dumpable {
+public class LegacyLockIconViewController implements Dumpable, LockIconViewController {
private static final String TAG = "LockIconViewController";
private static final float sDefaultDensity =
(float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
@@ -189,7 +189,7 @@
};
@Inject
- public LockIconViewController(
+ public LegacyLockIconViewController(
@NonNull StatusBarStateController statusBarStateController,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull KeyguardViewController keyguardViewController,
@@ -262,6 +262,7 @@
/** Sets the LockIconView to the controller and rebinds any that depend on it. */
@SuppressLint("ClickableViewAccessibility")
+ @Override
public void setLockIconView(LockIconView lockIconView) {
mView = lockIconView;
mView.setAccessibilityDelegate(mAccessibilityDelegate);
@@ -344,10 +345,12 @@
}
}
+ @Override
public float getTop() {
return mView.getLocationTop();
}
+ @Override
public float getBottom() {
return mView.getLocationBottom();
}
@@ -454,6 +457,7 @@
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) {
+ // positioning in this case is handled by [DefaultDeviceEntrySection]
mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
scaledPadding);
} else {
@@ -498,6 +502,7 @@
}
/** Every minute, update the aod icon's burn in offset */
+ @Override
public void dozeTimeTick() {
updateBurnInOffsets();
}
@@ -773,6 +778,7 @@
/**
* Set the alpha of this view.
*/
+ @Override
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
@@ -822,6 +828,7 @@
/**
* Whether the lock icon will handle a touch while dozing.
*/
+ @Override
public boolean willHandleTouchWhileDozing(MotionEvent event) {
// is in lock icon area
mView.getHitRect(mSensorTouchLocation);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
new file mode 100644
index 0000000..10d5a0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.view.MotionEvent
+
+/** Controls the [LockIconView]. */
+interface LockIconViewController {
+ fun setLockIconView(lockIconView: LockIconView)
+ fun getTop(): Float
+ fun getBottom(): Float
+ fun dozeTimeTick()
+ fun setAlpha(alpha: Float)
+ fun willHandleTouchWhileDozing(event: MotionEvent): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 22bd207..27b2b92 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -18,6 +18,7 @@
import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
+
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX;
import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
@@ -54,8 +55,8 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.wm.shell.animation.FlingAnimationUtils;
-import com.android.wm.shell.animation.PhysicsAnimator;
-import com.android.wm.shell.animation.PhysicsAnimator.SpringConfig;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.animation.PhysicsAnimator.SpringConfig;
import java.io.PrintWriter;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 1a9b01f..7e94804 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -29,8 +29,8 @@
import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index b26be0c..0cc3be2 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -99,9 +99,10 @@
if (Build.IS_DEBUGGABLE) {
// b/71353150 - looking for leaked binder proxies
BinderInternal.nSetBinderProxyCountEnabled(true);
- BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
+ BinderInternal.nSetBinderProxyCountWatermarks(
+ /* high= */ 1000, /* low= */ 900, /* warning= */ 950);
BinderInternal.setBinderProxyCountCallback(
- new BinderInternal.BinderProxyLimitListener() {
+ new BinderInternal.BinderProxyCountEventListener() {
@Override
public void onLimitReached(int uid) {
Slog.w(SystemUIApplication.TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
new file mode 100644
index 0000000..af8149f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import android.annotation.UiContext;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.AttachedSurfaceControl;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.UiThread;
+
+import com.android.systemui.res.R;
+
+import java.util.function.Supplier;
+
+class FullscreenMagnificationController {
+
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ private final WindowManager mWindowManager;
+ private Supplier<SurfaceControlViewHost> mScvhSupplier;
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+ private Rect mWindowBounds;
+ private SurfaceControl.Transaction mTransaction;
+ private View mFullscreenBorder = null;
+ private int mBorderOffset;
+ private final int mDisplayId;
+ private static final Region sEmptyRegion = new Region();
+
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier) {
+ mContext = context;
+ mAccessibilityManager = accessibilityManager;
+ mWindowManager = windowManager;
+ mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mTransaction = new SurfaceControl.Transaction();
+ mScvhSupplier = scvhSupplier;
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ mDisplayId = mContext.getDisplayId();
+ }
+
+ @UiThread
+ void onFullscreenMagnificationActivationChanged(boolean activated) {
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
+ }
+
+ @UiThread
+ private void removeFullscreenMagnificationBorder() {
+ if (mSurfaceControlViewHost != null) {
+ mSurfaceControlViewHost.release();
+ mSurfaceControlViewHost = null;
+ }
+
+ if (mFullscreenBorder != null) {
+ mFullscreenBorder = null;
+ }
+ }
+
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
+ @UiThread
+ private void createFullscreenMagnificationBorder() {
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+
+ SurfaceControl surfaceControl = mSurfaceControlViewHost
+ .getSurfacePackage().getSurfaceControl();
+
+ mTransaction
+ .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(surfaceControl, Integer.MAX_VALUE)
+ .show(surfaceControl)
+ .apply();
+
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+
+ applyTouchableRegion();
+ }
+
+ private LayoutParams getBorderLayoutParams() {
+ LayoutParams params = new LayoutParams(
+ mWindowBounds.width() + 2 * mBorderOffset,
+ mWindowBounds.height() + 2 * mBorderOffset,
+ LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+ LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.setTrustedOverlay();
+ return params;
+ }
+
+ private void applyTouchableRegion() {
+ // Sometimes this can get posted and run after deleteWindowMagnification() is called.
+ if (mFullscreenBorder == null) return;
+
+ AttachedSurfaceControl surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl();
+
+ // The touchable region of the mFullscreenBorder will be empty since we are going to allow
+ // all touch events to go through this view.
+ surfaceControl.setTouchableRegion(sEmptyRegion);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 88fa2de..70165f3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -34,6 +34,7 @@
import android.view.Display;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IMagnificationConnection;
@@ -134,6 +135,37 @@
@VisibleForTesting
DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
+ private static class FullscreenMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+ private final Context mContext;
+
+ FullscreenMagnificationControllerSupplier(Context context, Handler handler,
+ DisplayManager displayManager, SysUiState sysUiState,
+ SecureSettings secureSettings) {
+ super(displayManager);
+ mContext = context;
+ }
+
+ @Override
+ protected FullscreenMagnificationController createInstance(Display display) {
+ final Context windowContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> new SurfaceControlViewHost(
+ mContext, mContext.getDisplay(), new InputTransferToken(), TAG);
+ windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
+ return new FullscreenMagnificationController(
+ windowContext,
+ windowContext.getSystemService(AccessibilityManager.class),
+ windowContext.getSystemService(WindowManager.class),
+ scvhSupplier);
+ }
+ }
+
+ @VisibleForTesting
+ DisplayIdIndexSupplier<FullscreenMagnificationController>
+ mFullscreenMagnificationControllerSupplier;
+
private static class SettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
@@ -185,6 +217,8 @@
mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
+ mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
+ context, mHandler, displayManager, sysUiState, secureSettings);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
@@ -273,8 +307,13 @@
}
}
+ @MainThread
void onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
- // Do nothing
+ final FullscreenMagnificationController fullscreenMagnificationController =
+ mFullscreenMagnificationControllerSupplier.get(displayId);
+ if (fullscreenMagnificationController != null) {
+ fullscreenMagnificationController.onFullscreenMagnificationActivationChanged(activated);
+ }
}
@MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index a90d4b2..c1b3962 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -39,9 +39,9 @@
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.wm.shell.R
-import com.android.wm.shell.animation.PhysicsAnimator
import com.android.wm.shell.common.bubbles.DismissCircleView
import com.android.wm.shell.common.bubbles.DismissView
+import com.android.wm.shell.shared.animation.PhysicsAnimator
/**
* View that handles interactions between DismissCircleView and BubbleStackView.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index 35fe6b1..c464c82 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -22,7 +22,6 @@
import android.annotation.IntDef;
import android.content.ComponentCallbacks;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -37,7 +36,6 @@
import androidx.annotation.NonNull;
-import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -171,9 +169,9 @@
mTextView.setTextColor(textColor);
mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
- final ColorStateList colorAccent = Utils.getColorAccent(getContext());
mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
- mUndoButton.setTextColor(colorAccent);
+ mUndoButton.setTextColor(textColor);
+ mUndoButton.setAllCaps(true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 1f5a0bf..be75e10 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -36,7 +36,6 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.expresslog.Counter;
import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
@@ -418,18 +417,11 @@
onPositionChanged();
}
- void incrementTexMetricForAllTargets(String metric) {
+ void incrementTexMetric(String metric) {
if (!Flags.floatingMenuDragToEdit()) {
return;
}
- for (AccessibilityTarget target : mTargetFeatures) {
- incrementTexMetric(metric, target.getUid());
- }
- }
-
- @VisibleForTesting
- void incrementTexMetric(String metric, int uid) {
- Counter.logIncrementWithUid(metric, uid);
+ Counter.logIncrement(metric);
}
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 85bf784..86279be 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -105,14 +105,14 @@
*
* <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
*/
- static final String TEX_METRIC_DISMISS = "accessibility.value_fab_shortcut_action_dismiss";
+ static final String TEX_METRIC_DISMISS = "accessibility.value_fab_shortcut_dismiss";
/**
* Counter indicating the FAB was dragged to the Edit action button.
*
* <p>Defined in frameworks/proto_logging/stats/express/catalog/accessibility.cfg.
*/
- static final String TEX_METRIC_EDIT = "accessibility.value_fab_shortcut_action_edit";
+ static final String TEX_METRIC_EDIT = "accessibility.value_fab_shortcut_edit";
private final WindowManager mWindowManager;
private final MenuView mMenuView;
@@ -492,11 +492,11 @@
} else {
hideMenuAndShowMessage();
}
- mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_DISMISS);
+ mMenuView.incrementTexMetric(TEX_METRIC_DISMISS);
} else if (id == R.id.action_edit
&& Flags.floatingMenuDragToEdit()) {
gotoEditScreen();
- mMenuView.incrementTexMetricForAllTargets(TEX_METRIC_EDIT);
+ mMenuView.incrementTexMetric(TEX_METRIC_EDIT);
}
mDismissView.hide();
mDragToInteractView.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
new file mode 100644
index 0000000..4733d06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+/**
+ * Dialog for showing hearing devices controls.
+ */
+public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate{
+
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
+
+ private SystemUIDialog mDialog;
+
+ /** Factory to create a {@link HearingDevicesDialogDelegate} dialog instance. */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a {@link HearingDevicesDialogDelegate} instance */
+ HearingDevicesDialogDelegate create();
+ }
+
+ @AssistedInject
+ public HearingDevicesDialogDelegate(
+ SystemUIDialog.Factory systemUIDialogFactory) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
+ }
+
+ @Override
+ public SystemUIDialog createDialog() {
+ SystemUIDialog dialog = mSystemUIDialogFactory.create(this);
+
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ mDialog = dialog;
+
+ return dialog;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
new file mode 100644
index 0000000..c83043e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.animation.DialogCuj;
+import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import javax.inject.Inject;
+
+/**
+ * Factory to create {@link HearingDevicesDialogDelegate} objects and manage its lifecycle.
+ */
+@SysUISingleton
+public class HearingDevicesDialogManager {
+
+ private static final boolean DEBUG = true;
+ private static final String TAG = "HearingDevicesDialogManager";
+ private static final String INTERACTION_JANK_TAG = "hearing_devices_tile";
+ private SystemUIDialog mDialog;
+ private final DialogTransitionAnimator mDialogTransitionAnimator;
+ private final HearingDevicesDialogDelegate.Factory mDialogFactory;
+
+ @Inject
+ public HearingDevicesDialogManager(
+ DialogTransitionAnimator dialogTransitionAnimator,
+ HearingDevicesDialogDelegate.Factory dialogFactory) {
+ mDialogTransitionAnimator = dialogTransitionAnimator;
+ mDialogFactory = dialogFactory;
+ }
+
+ /**
+ * Shows the dialog.
+ *
+ * @param view The view from which the dialog is shown.
+ */
+ public void showDialog(View view) {
+ if (mDialog != null) {
+ if (DEBUG) {
+ Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first.");
+ }
+ destroyDialog();
+ }
+ mDialog = mDialogFactory.create().createDialog();
+
+ if (view != null) {
+ mDialogTransitionAnimator.showFromView(mDialog, view,
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG),
+ true);
+ } else {
+ mDialog.show();
+ }
+ }
+
+ private void destroyDialog() {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 4047623..7cb028a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.qs.tiles.ColorInversionTile
import com.android.systemui.qs.tiles.DreamTile
import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.qs.tiles.HearingDevicesTile
import com.android.systemui.qs.tiles.NightDisplayTile
import com.android.systemui.qs.tiles.OneHandedModeTile
import com.android.systemui.qs.tiles.ReduceBrightColorsTile
@@ -94,6 +95,12 @@
@StringKey(FontScalingTile.TILE_SPEC)
fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
+ /** Inject HearingDevicesTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(HearingDevicesTile.TILE_SPEC)
+ fun bindHearingDevicesTile(hearingDevicesTile: HearingDevicesTile): QSTileImpl<*>
+
companion object {
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index ef686f9..4d328d6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -72,7 +72,7 @@
.filterNotNull()
// TODO(b/322787129): Also set a custom transition animation here to avoid the regular
// slide-in animation when setting the scene programmatically
- .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
+ .onEach { nextScene -> communalInteractor.changeScene(nextScene) }
.launchIn(applicationScope)
// TODO(b/322787129): re-enable once custom animations are in place
@@ -129,7 +129,7 @@
.sample(keyguardInteractor.isDreaming, ::Pair)
.collect { (shouldTimeout, isDreaming) ->
if (isDreaming && shouldTimeout) {
- communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ communalInteractor.changeScene(CommunalScenes.Blank)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt
new file mode 100644
index 0000000..5e41a1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 82d9437..72dcb26 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -23,11 +23,19 @@
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineScope
@Module(
includes =
@@ -47,4 +55,24 @@
fun bindEditWidgetsActivityStarter(
starter: EditWidgetsActivityStarterImpl
): EditWidgetsActivityStarter
+
+ @Binds
+ @Communal
+ fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
+
+ companion object {
+ @Provides
+ @Communal
+ @SysUISingleton
+ fun providesCommunalSceneDataSourceDelegator(
+ @Application applicationScope: CoroutineScope
+ ): SceneDataSourceDelegator {
+ val config =
+ SceneContainerConfig(
+ sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
+ initialSceneKey = CommunalScenes.Blank
+ )
+ return SceneDataSourceDelegator(applicationScope, config)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 201ce83..8bfd8d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -18,11 +18,12 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.communal.dagger.Communal
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneDataSource
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +31,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -38,16 +38,15 @@
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/**
- * Target scene as requested by the underlying [SceneTransitionLayout] or through
- * [setDesiredScene].
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*/
- val desiredScene: StateFlow<SceneKey>
+ val currentScene: StateFlow<SceneKey>
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableTransitionState>
/** Updates the requested scene. */
- fun setDesiredScene(desiredScene: SceneKey)
+ fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null)
/**
* Updates the transition state of the hub [SceneTransitionLayout].
@@ -63,12 +62,10 @@
@Inject
constructor(
@Background backgroundScope: CoroutineScope,
- sceneContainerFlags: SceneContainerFlags,
- sceneContainerRepository: SceneContainerRepository,
+ @Communal private val sceneDataSource: SceneDataSource,
) : CommunalRepository {
- private val _desiredScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default)
- override val desiredScene: StateFlow<SceneKey> = _desiredScene.asStateFlow()
+ override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene
private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
@@ -81,8 +78,8 @@
initialValue = defaultTransitionState,
)
- override fun setDesiredScene(desiredScene: SceneKey) {
- _desiredScene.value = desiredScene
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+ sceneDataSource.changeScene(toScene, transitionKey)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 52025b1..246d5d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -25,6 +25,7 @@
import android.provider.Settings
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalPrefsRepository
@@ -80,6 +81,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -142,13 +144,12 @@
)
/**
- * Target scene as requested by the underlying [SceneTransitionLayout] or through
- * [onSceneChanged].
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
*
* If [isCommunalAvailable] is false, will return [CommunalScenes.Blank]
*/
val desiredScene: Flow<SceneKey> =
- communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available ->
+ communalRepository.currentScene.combine(isCommunalAvailable) { scene, available ->
if (available) scene else CommunalScenes.Blank
}
@@ -239,10 +240,14 @@
* This will not be true while transitioning to the hub and will turn false immediately when a
* swipe to exit the hub starts.
*/
- val isIdleOnCommunal: Flow<Boolean> =
- communalRepository.transitionState.map {
- it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal
- }
+ val isIdleOnCommunal: StateFlow<Boolean> =
+ communalRepository.transitionState
+ .map { it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
/**
* Flow that emits a boolean if any portion of the communal UI is visible at all.
@@ -254,9 +259,12 @@
!(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank)
}
- /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
- fun onSceneChanged(newScene: SceneKey) {
- communalRepository.setDesiredScene(newScene)
+ /**
+ * Asks for an asynchronous scene witch to [newScene], which will use the corresponding
+ * installed transition or the one specified by [transitionKey], if provided.
+ */
+ fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) {
+ communalRepository.changeScene(newScene, transitionKey)
}
fun setEditModeOpen(isOpen: Boolean) {
@@ -264,8 +272,11 @@
}
/** Show the widget editor Activity. */
- fun showWidgetEditor(preselectedKey: String? = null) {
- editWidgetsActivityStarter.startActivity(preselectedKey)
+ fun showWidgetEditor(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ ) {
+ editWidgetsActivityStarter.startActivity(preselectedKey, shouldOpenWidgetPickerOnStart)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index fc8d658..7061227 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -91,13 +91,6 @@
override val size = CommunalContentSize.HALF
}
- /** A CTA tile in the glanceable hub edit model which remains visible in the grid. */
- class CtaTileInEditMode : CommunalContentModel {
- override val key: String = KEY.CTA_TILE_IN_EDIT_MODE_KEY
- // Same as widget size.
- override val size = CommunalContentSize.HALF
- }
-
class Tutorial(
id: Int,
override var size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index c913300..095222a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -49,8 +49,8 @@
communalInteractor.signalUserInteraction()
}
- fun onSceneChanged(scene: SceneKey) {
- communalInteractor.onSceneChanged(scene)
+ fun changeScene(scene: SceneKey) {
+ communalInteractor.changeScene(scene)
}
/**
@@ -87,6 +87,9 @@
/** Whether the popup message triggered by dismissing the CTA tile is showing. */
open val isPopupOnDismissCtaShowing: Flow<Boolean> = flowOf(false)
+ /** Whether the communal hub is empty with no widget available. */
+ open val isEmptyState: Flow<Boolean> = flowOf(false)
+
/** Hide the popup message triggered by dismissing the CTA tile. */
open fun onHidePopupAfterDismissCta() {}
@@ -103,7 +106,10 @@
open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
- open fun onOpenWidgetEditor(preselectedKey: String? = null) {}
+ open fun onOpenWidgetEditor(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ ) {}
/** Called as the UI requests to dismiss the CTA tile. */
open fun onDismissCtaTile() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index afa7c37..b3002cd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -43,7 +43,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -66,11 +65,9 @@
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent
- .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
- .onEach { models ->
- logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
- }
+ communalInteractor.widgetContent.onEach { models ->
+ logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
+ }
private val _reorderingWidgets = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 6e69ed7..c73d738 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -41,8 +41,10 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -83,6 +85,12 @@
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ override val isEmptyState: Flow<Boolean> =
+ communalInteractor.widgetContent
+ .map { it.isEmpty() }
+ .distinctUntilChanged()
+ .onEach { logger.d("isEmptyState: $it") }
+
private val _isPopupOnDismissCtaShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
@@ -112,8 +120,10 @@
}
}
- override fun onOpenWidgetEditor(preselectedKey: String?) =
- communalInteractor.showWidgetEditor(preselectedKey)
+ override fun onOpenWidgetEditor(
+ preselectedKey: String?,
+ shouldOpenWidgetPickerOnStart: Boolean,
+ ) = communalInteractor.showWidgetEditor(preselectedKey, shouldOpenWidgetPickerOnStart)
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index ba18f01..5f4b394 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -31,7 +31,6 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.coroutines.launch
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
@@ -60,12 +59,15 @@
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
const val EXTRA_PRESELECTED_KEY = "preselected_key"
+ const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
}
private val logger = Logger(logBuffer, "EditWidgetsActivity")
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
+ private var shouldOpenWidgetPickerOnStart = false
+
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
@@ -112,6 +114,9 @@
window.setDecorFitsSystemWindows(false)
val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
+ shouldOpenWidgetPickerOnStart =
+ intent.getBooleanExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, false)
+
communalViewModel.setSelectedKey(preselectedKey)
setContent {
@@ -144,7 +149,7 @@
private fun onEditDone() {
try {
- communalViewModel.onSceneChanged(CommunalScenes.Communal)
+ communalViewModel.changeScene(CommunalScenes.Communal)
checkNotNull(windowManagerService).lockNow(/* options */ null)
finish()
} catch (e: RemoteException) {
@@ -162,6 +167,11 @@
override fun onStart() {
super.onStart()
+ if (shouldOpenWidgetPickerOnStart) {
+ onOpenWidgetPicker()
+ shouldOpenWidgetPickerOnStart = false
+ }
+
logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index d1843af..76be005 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -18,13 +18,17 @@
import android.content.Context
import android.content.Intent
+import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_OPEN_WIDGET_PICKER_ON_START
import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
interface EditWidgetsActivityStarter {
- fun startActivity(preselectedKey: String? = null)
+ fun startActivity(
+ preselectedKey: String? = null,
+ shouldOpenWidgetPickerOnStart: Boolean = false,
+ )
}
class EditWidgetsActivityStarterImpl
@@ -34,11 +38,14 @@
private val activityStarter: ActivityStarter,
) : EditWidgetsActivityStarter {
- override fun startActivity(preselectedKey: String?) {
+ override fun startActivity(preselectedKey: String?, shouldOpenWidgetPickerOnStart: Boolean) {
activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .apply { preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) } },
+ .apply {
+ preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) }
+ putExtra(EXTRA_OPEN_WIDGET_PICKER_ON_START, shouldOpenWidgetPickerOnStart)
+ },
/* onlyProvisioned = */ true,
/* dismissShade = */ true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 3b0c281..e104166 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -36,11 +36,11 @@
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
-import com.android.wm.shell.transition.ShellTransitions;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 19af371..1ed4b50 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,6 +49,7 @@
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -89,6 +90,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recordissue.RecordIssueModule;
import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.scene.shared.model.SceneContainerConfig;
import com.android.systemui.scene.shared.model.SceneDataSource;
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
@@ -165,6 +167,8 @@
import javax.inject.Named;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* A dagger module for injecting components of System UI that are required by System UI.
*
@@ -402,6 +406,13 @@
@ClassKey(SystemUISecondaryUserService.class)
abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service);
+ @Provides
+ @SysUISingleton
+ static SceneDataSourceDelegator providesSceneDataSourceDelegator(
+ @Application CoroutineScope applicationScope, SceneContainerConfig config) {
+ return new SceneDataSourceDelegator(applicationScope, config);
+ }
+
@Binds
abstract SceneDataSource bindSceneDataSource(SceneDataSourceDelegator delegator);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index dfec771..e04a0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,7 +23,6 @@
import com.android.systemui.SystemUIInitializer;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
@@ -32,11 +31,12 @@
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
-import com.android.wm.shell.transition.ShellTransitions;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index 71b5ab2..b8c03c0 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,9 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.deviceentry
+import com.android.keyguard.EmptyLockIconViewController
+import com.android.keyguard.LegacyLockIconViewController
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.Multibinds
@Module(
@@ -18,4 +41,19 @@
* A set of DeviceEntryIconTransitions. Ensures that this can be injected even if it's empty.
*/
@Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition>
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideLockIconViewController(
+ legacyLockIconViewController: Lazy<LegacyLockIconViewController>,
+ emptyLockIconViewController: Lazy<EmptyLockIconViewController>,
+ ): LockIconViewController {
+ return if (DeviceEntryUdfpsRefactor.isEnabled) {
+ emptyLockIconViewController.get()
+ } else {
+ legacyLockIconViewController.get()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 7f3b5eb..926f7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -172,19 +172,18 @@
final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
/ mTouchSession.getBounds().height();
setPanelExpansion(mBouncerInitiallyShowing
- ? screenTravelPercentage : 1 - screenTravelPercentage, dragDownAmount);
+ ? screenTravelPercentage : 1 - screenTravelPercentage);
return true;
}
};
- private void setPanelExpansion(float expansion, float dragDownAmount) {
+ private void setPanelExpansion(float expansion) {
mCurrentExpansion = expansion;
ShadeExpansionChangeEvent event =
new ShadeExpansionChangeEvent(
/* fraction= */ mCurrentExpansion,
/* expanded= */ mExpanded,
- /* tracking= */ true,
- /* dragDownPxAmount= */ dragDownAmount);
+ /* tracking= */ true);
mCurrentScrimController.expand(event);
}
@@ -333,7 +332,7 @@
animation -> {
float expansionFraction = (float) animation.getAnimatedValue();
float dragDownAmount = expansionFraction * expansionHeight;
- setPanelExpansion(expansionFraction, dragDownAmount);
+ setPanelExpansion(expansionFraction);
});
if (!mBouncerInitiallyShowing
&& targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 1b832d4..037c23b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -57,7 +57,7 @@
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
if (showGlanceableHub) {
toGlanceableHubTransitionViewModel.startTransition()
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ communalInteractor.changeScene(CommunalScenes.Communal)
} else {
toLockscreenTransitionViewModel.startTransition()
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 19a44cc..d33e7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -394,10 +394,6 @@
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
- /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
- @JvmField
- val MULTI_DISPLAY_SCREENSHOT = releasedFlag("multi_display_screenshot")
-
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index 1705909..f4998a7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -18,10 +18,10 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.launch
class QSLongPressEffectViewBinder {
@@ -31,16 +31,18 @@
fun bind(
tile: QSTileViewImpl,
+ tileSpec: String?,
effect: QSLongPressEffect?,
) {
if (effect == null) return
handle =
tile.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
effect.scope = this
+ val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
- launch {
+ launch("$tag#progress") {
effect.effectProgress.collect { progress ->
progress?.let {
if (it == 0f) {
@@ -51,7 +53,7 @@
}
}
- launch {
+ launch("$tag#action") {
effect.actionType.collect { action ->
action?.let {
when (it) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 3134e35..6b53f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -84,8 +84,8 @@
import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 53c81e5..cf83582 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -20,7 +20,7 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.WallpaperManager
-import android.content.Context
+import android.content.res.Resources
import android.graphics.Matrix
import android.graphics.Rect
import android.os.DeadObjectException
@@ -32,16 +32,18 @@
import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
+import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.core.math.MathUtils
import com.android.app.animation.Interpolators
import com.android.internal.R
import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags.fastUnlockTransition
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.Flags.fastUnlockTransition
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.shared.system.ActivityManagerWrapper
@@ -145,17 +147,18 @@
*/
@SysUISingleton
class KeyguardUnlockAnimationController @Inject constructor(
- private val context: Context,
- private val keyguardStateController: KeyguardStateController,
- private val
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val
keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -399,7 +402,7 @@
keyguardStateController.addCallback(this)
roundedCornerRadius =
- context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+ resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
/**
@@ -438,7 +441,7 @@
Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
"${!notificationShadeWindowController.isLaunchingActivity}")
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
- Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(context)}")
+ Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
/**
@@ -1100,7 +1103,7 @@
// We don't do the shared element on large screens because the smartspace has to fly across
// large distances, which is distracting.
- if (Utilities.isLargeScreen(context)) {
+ if (Utilities.isLargeScreen(windowManager, resources)) {
return false
}
@@ -1180,8 +1183,8 @@
companion object {
- fun isFoldable(context: Context): Boolean {
- return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
+ fun isFoldable(resources: Resources): Boolean {
+ return resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index fa845c7..30beca7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -29,11 +29,14 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.LegacyLockIconViewController
import com.android.keyguard.LockIconView
-import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
@@ -92,7 +95,7 @@
private val configuration: ConfigurationState,
private val context: Context,
private val keyguardIndicationController: KeyguardIndicationController,
- private val lockIconViewController: Lazy<LockIconViewController>,
+ private val lockIconViewController: Lazy<LegacyLockIconViewController>,
private val shadeInteractor: ShadeInteractor,
private val interactionJankMonitor: InteractionJankMonitor,
private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
@@ -109,6 +112,7 @@
private var rootViewHandle: DisposableHandle? = null
private var indicationAreaHandle: DisposableHandle? = null
+ private val sceneKey = SceneKey("root-view-scene-key")
var keyguardStatusViewController: KeyguardStatusViewController? = null
get() {
@@ -219,12 +223,25 @@
blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
return ComposeView(context).apply {
setContent {
- LockscreenContent(
- viewModel = viewModel,
- blueprints = sceneBlueprints,
- clockInteractor = clockInteractor
- )
- .Content(modifier = Modifier.fillMaxSize())
+ // STL is used solely to provide a SceneScope to enable us to invoke SceneScope
+ // composables.
+ SceneTransitionLayout(
+ currentScene = sceneKey,
+ onChangeScene = {},
+ transitions = transitions {},
+ ) {
+ scene(sceneKey) {
+ with(
+ LockscreenContent(
+ viewModel = viewModel,
+ blueprints = sceneBlueprints,
+ clockInteractor = clockInteractor
+ )
+ ) {
+ Content(modifier = Modifier.fillMaxSize())
+ }
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 182798e..53aee5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2520,18 +2520,8 @@
String message = "";
switch (msg.what) {
case SHOW:
- // There is a potential race condition when SysUI starts up. CentralSurfaces
- // must invoke #registerCentralSurfaces on this class before any messages can be
- // processed. If this happens, repost the message with a small delay and try
- // again.
- if (mCentralSurfaces == null) {
- message = "DELAYING SHOW";
- Message newMsg = mHandler.obtainMessage(SHOW, (Bundle) msg.obj);
- mHandler.sendMessageDelayed(newMsg, 100);
- } else {
- message = "SHOW";
- handleShow((Bundle) msg.obj);
- }
+ message = "SHOW";
+ handleShow((Bundle) msg.obj);
break;
case HIDE:
message = "HIDE";
@@ -2618,18 +2608,8 @@
Trace.endSection();
break;
case SYSTEM_READY:
- // There is a potential race condition when SysUI starts up. CentralSurfaces
- // must invoke #registerCentralSurfaces on this class before any messages can be
- // processed. If this happens, repost the message with a small delay and try
- // again.
- if (mCentralSurfaces == null) {
- message = "DELAYING SYSTEM_READY";
- Message newMsg = mHandler.obtainMessage(SYSTEM_READY);
- mHandler.sendMessageDelayed(newMsg, 100);
- } else {
- message = "SYSTEM_READY";
- handleSystemReady();
- }
+ message = "SYSTEM_READY";
+ handleSystemReady();
break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 88eadd7..b3d9a76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -92,7 +92,8 @@
walletController.setupWalletChangeObservers(
callback,
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
)
withContext(backgroundDispatcher) {
@@ -104,7 +105,8 @@
awaitClose {
walletController.unregisterWalletChangeObservers(
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
index fa6efa5..f763e62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -17,14 +17,17 @@
package com.android.systemui.keyguard.domain.backup
+import android.app.backup.BackupDataInputStream
import android.app.backup.SharedPreferencesBackupHelper
import android.content.Context
+import android.util.Log
+import com.android.app.tracing.traceSection
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.settings.UserFileManagerImpl
/** Handles backup & restore for keyguard quick affordances. */
class KeyguardQuickAffordanceBackupHelper(
- context: Context,
+ private val context: Context,
userId: Int,
) :
SharedPreferencesBackupHelper(
@@ -34,4 +37,17 @@
fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
)
.getPath()
- )
+ ) {
+
+ override fun restoreEntity(data: BackupDataInputStream?) {
+ Log.d(TAG, "Starting restore for ${data?.key} for user ${context.userId}")
+ traceSection("$TAG File restore: ${data?.key}") {
+ super.restoreEntity(data)
+ }
+ Log.d(TAG, "Finished restore for ${data?.key} for user ${context.userId}")
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceBackupHelper"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 6bedb89..b6289d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -185,5 +185,6 @@
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 933.milliseconds
val TO_AOD_DURATION = DEFAULT_DURATION
+ val TO_DOZING_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 9aa2202..03ed567 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -19,13 +19,17 @@
import android.app.ActivityManager.RunningTaskInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -45,11 +49,12 @@
class KeyguardOcclusionInteractor
@Inject
constructor(
- @Application scope: CoroutineScope,
- val repository: KeyguardOcclusionRepository,
- val powerInteractor: PowerInteractor,
- val transitionInteractor: KeyguardTransitionInteractor,
- val keyguardInteractor: KeyguardInteractor,
+ @Application applicationScope: CoroutineScope,
+ private val repository: KeyguardOcclusionRepository,
+ private val powerInteractor: PowerInteractor,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
) {
val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
@@ -94,14 +99,19 @@
// Emit false once that activity goes away.
isShowWhenLockedActivityOnTop.filter { !it }.map { false }
)
- .stateIn(scope, SharingStarted.Eagerly, false)
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
/**
* Whether launching an occluding activity will automatically dismiss keyguard. This happens if
* the keyguard is dismissable.
*/
- val occludingActivityWillDismissKeyguard =
- keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+ val occludingActivityWillDismissKeyguard: StateFlow<Boolean> =
+ if (SceneContainerFlag.isEnabled) {
+ deviceUnlockedInteractor.get().isDeviceUnlocked
+ } else {
+ keyguardInteractor.isKeyguardDismissible
+ }
+ .stateIn(scope = applicationScope, SharingStarted.Eagerly, false)
/**
* Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index a64bb99..97081d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -86,9 +86,11 @@
private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
return transitionValueCache.getOrPut(state) {
MutableSharedFlow<Float>(
- extraBufferCapacity = 2,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
+ replay = 1,
+ extraBufferCapacity = 2,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ .also { it.tryEmit(0f) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 4cb342b..1eea556 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -19,14 +19,16 @@
import android.graphics.PixelFormat
import android.view.Gravity
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.CoreStartable
-import com.android.systemui.biometrics.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -70,8 +72,9 @@
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
- Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
- PixelFormat.TRANSLUCENT
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT,
)
.apply {
title = "AlternateBouncerView"
@@ -113,8 +116,36 @@
}
windowManager.get().removeView(alternateBouncerView)
+ alternateBouncerView!!.removeOnAttachStateChangeListener(onAttachAddBackGestureHandler)
+ alternateBouncerView = null
}
+ private val onAttachAddBackGestureHandler =
+ object : View.OnAttachStateChangeListener {
+ private val onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
+ onBackRequested()
+ }
+
+ override fun onViewAttachedToWindow(view: View) {
+ view
+ .findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_OVERLAY,
+ onBackInvokedCallback,
+ )
+ }
+
+ override fun onViewDetachedFromWindow(view: View) {
+ view
+ .findOnBackInvokedDispatcher()
+ ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+ }
+
+ fun onBackRequested() {
+ alternateBouncerDependencies.get().viewModel.hideAlternateBouncer()
+ }
+ }
+
private fun addViewToWindowManager() {
if (alternateBouncerView?.isAttachedToWindow == true) {
return
@@ -125,6 +156,7 @@
as ConstraintLayout
windowManager.get().addView(alternateBouncerView, layoutParams)
+ alternateBouncerView!!.addOnAttachStateChangeListener(onAttachAddBackGestureHandler)
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index d9f12c3..5906cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.binder
import android.content.Context
+import android.util.DisplayMetrics
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
@@ -109,7 +110,7 @@
private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
constraints.apply {
constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
- constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
val largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
context.resources.getDimensionPixelSize(
@@ -129,7 +130,29 @@
ConstraintSet.END
)
- connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ // In preview, we'll show UDFPS icon for UDFPS devices
+ // and nothing for non-UDFPS devices,
+ // but we need position of device entry icon to constrain clock
+ if (getConstraint(R.id.lock_icon_view) != null) {
+ connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ } else {
+ // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+ val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+ connect(
+ R.id.lockscreen_clock_view_large,
+ BOTTOM,
+ PARENT_ID,
+ BOTTOM,
+ clockBottomMargin
+ )
+ }
+
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 4d9354dd..33052be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -82,7 +82,6 @@
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardRootViewBinder {
-
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
@@ -102,14 +101,6 @@
): DisposableHandle {
var onLayoutChangeListener: OnLayoutChange? = null
val childViews = mutableMapOf<Int, View>()
- val statusViewId = R.id.keyguard_status_view
- val burnInLayerId = R.id.burn_in_layer
- val aodNotificationIconContainerId = R.id.aod_notification_icon_container
- val largeClockId = R.id.lockscreen_clock_view_large
- val indicationArea = R.id.keyguard_indication_area
- val startButton = R.id.start_button
- val endButton = R.id.end_button
- val lockIcon = R.id.lock_icon_view
if (KeyguardBottomAreaRefactor.isEnabled) {
view.setOnTouchListener { _, event ->
@@ -214,7 +205,7 @@
val px = state.value ?: return@collect
when {
state.isToOrFrom(KeyguardState.AOD) -> {
- childViews[largeClockId]?.translationX = px
+ // Large Clock is not translated in the x direction
childViews[burnInLayerId]?.translationX = px
childViews[aodNotificationIconContainerId]?.translationX =
px
@@ -436,7 +427,7 @@
oldRight: Int,
oldBottom: Int
) {
- childViews[R.id.nssl_placeholder]?.let { notificationListPlaceholder ->
+ childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
// After layout, ensure the notifications are positioned correctly
viewModel.onNotificationContainerBoundsChanged(
notificationListPlaceholder.top.toFloat(),
@@ -460,14 +451,14 @@
)
}
} else {
- childViews[R.id.keyguard_status_view]?.top ?: 0
+ childViews[statusViewId]?.top ?: 0
}
)
}
}
private fun isUserVisible(view: View): Boolean {
- return view.id != R.id.burn_in_layer &&
+ return view.id != burnInLayerId &&
view.visibility == VISIBLE &&
view.width > 0 &&
view.height > 0
@@ -589,6 +580,17 @@
private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+ private val statusViewId = R.id.keyguard_status_view
+ private val burnInLayerId = R.id.burn_in_layer
+ private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+ private val largeClockId = R.id.lockscreen_clock_view_large
+ private val smallClockId = R.id.lockscreen_clock_view
+ private val indicationArea = R.id.keyguard_indication_area
+ private val startButton = R.id.start_button
+ private val endButton = R.id.end_button
+ private val lockIcon = R.id.lock_icon_view
+ private val nsslPlaceholderId = R.id.nssl_placeholder
+
private const val ID = "occluding_app_device_entry_unlock_msg"
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 881467f..4a09939 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -25,6 +25,7 @@
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.GONE
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
@@ -160,7 +161,7 @@
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(R.id.lockscreen_clock_view_large, END, guideline, END)
- connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+ connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
context.resources.getDimensionPixelSize(
@@ -172,7 +173,7 @@
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainWidth(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
- constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
+ constrainHeight(R.id.lockscreen_clock_view_large, MATCH_CONSTRAINT)
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 4c846e4..29041d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -34,6 +34,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
+import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -72,7 +73,7 @@
override fun addViews(constraintLayout: ConstraintLayout) {
if (
!KeyguardBottomAreaRefactor.isEnabled &&
- !DeviceEntryUdfpsRefactor.isEnabled &&
+ !MigrateClocksToBlueprint.isEnabled &&
!DeviceEntryUdfpsRefactor.isEnabled
) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index ac2713d..8c6be98 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +41,7 @@
constructor(
bouncerToGoneFlows: BouncerToGoneFlows,
animationFlow: KeyguardTransitionAnimationFlow,
+ private val statusBarStateController: SysuiStatusBarStateController,
) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
@@ -59,6 +61,30 @@
)
}
+ fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ var leaveShadeOpen = false
+
+ return transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ startAlpha = viewState.alpha()
+ },
+ onStep = {
+ if (leaveShadeOpen) {
+ 1f
+ } else {
+ MathUtils.lerp(startAlpha, 0f, it)
+ }
+ },
+ )
+ }
+
+ /** See [BouncerToGoneFlows#showAllNotifications] */
+ val showAllNotifications: Flow<Boolean> =
+ bouncerToGoneFlows.showAllNotifications(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 924fc5d..fe88b81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -32,6 +32,7 @@
import kotlin.time.Duration
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -63,6 +64,31 @@
}
}
+ /**
+ * When the shade is expanded, make sure that all notifications can be seen immediately during a
+ * transition to GONE. This matters especially when the user has chosen to not show
+ * notifications on the lockscreen and then pulls down the shade, which presents them with an
+ * immediate auth prompt, followed by a notification animation.
+ */
+ fun showAllNotifications(duration: Duration, from: KeyguardState): Flow<Boolean> {
+ var leaveShadeOpen = false
+ return animationFlow
+ .setup(
+ duration = duration,
+ from = from,
+ to = GONE,
+ )
+ .sharedFlow(
+ duration = duration,
+ onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+ onStep = { if (leaveShadeOpen) 1f else 0f },
+ onFinish = { 0f },
+ onCancel = { 0f },
+ )
+ .map { it == 1f }
+ .distinctUntilChanged()
+ }
+
private fun createScrimAlphaFlow(
duration: Duration,
fromState: KeyguardState,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 1b91c49..87324a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -20,6 +20,7 @@
import android.content.Context
import com.android.settingslib.Utils
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import javax.inject.Inject
@@ -34,6 +35,7 @@
/** Models the UI state for the device entry icon background view. */
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
@ExperimentalCoroutinesApi
+@SysUISingleton
class DeviceEntryBackgroundViewModel
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 6281097..0aa6d12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.Utils
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -39,6 +40,7 @@
/** Models the UI state for the device entry icon foreground view (displayed icon). */
@ExperimentalCoroutinesApi
+@SysUISingleton
class DeviceEntryForegroundViewModel
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index bc4fd1c..49fffdd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -19,6 +19,7 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
@@ -52,6 +53,7 @@
/** Models the UI state for the containing device entry icon & long-press handling view. */
@ExperimentalCoroutinesApi
+@SysUISingleton
class DeviceEntryIconViewModel
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5337ca3..e8313a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -102,6 +102,7 @@
private val lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel,
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
+ private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
@@ -228,6 +229,7 @@
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
+ occludedToDozingTransitionViewModel.lockscreenAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 1f80441..36896f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -18,8 +18,10 @@
import android.content.res.Resources
import com.android.keyguard.KeyguardClockSwitch
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.res.R
@@ -29,6 +31,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -42,6 +45,7 @@
private val authController: AuthController,
val longPress: KeyguardLongPressViewModel,
val shadeInteractor: ShadeInteractor,
+ @Application private val applicationScope: CoroutineScope,
) {
private val clockSize = clockInteractor.clockSize
@@ -50,11 +54,26 @@
val isLargeClockVisible: Boolean
get() = clockSize.value == KeyguardClockSwitch.LARGE
- val areNotificationsVisible: Boolean
- get() = !isLargeClockVisible || shouldUseSplitNotificationShade
+ val shouldUseSplitNotificationShade: StateFlow<Boolean> =
+ shadeInteractor.shadeMode
+ .map { it == ShadeMode.Split }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
- val shouldUseSplitNotificationShade: Boolean
- get() = shadeInteractor.shadeMode.value == ShadeMode.Split
+ val areNotificationsVisible: StateFlow<Boolean> =
+ combine(clockSize, shouldUseSplitNotificationShade) {
+ clockSize,
+ shouldUseSplitNotificationShade ->
+ clockSize == SMALL || shouldUseSplitNotificationShade
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (isLargeClockVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index 4e6aa03..f03625e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,6 +38,7 @@
@Inject
constructor(
animationFlow: KeyguardTransitionAnimationFlow,
+ private val statusBarStateController: SysuiStatusBarStateController,
) : DeviceEntryIconTransition {
private val transitionAnimation: FlowBuilder =
@@ -54,6 +56,26 @@
onCancel = { 1f },
)
+ fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ var leaveShadeOpen = false
+
+ return transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ startAlpha = viewState.alpha()
+ },
+ onStep = {
+ if (leaveShadeOpen) {
+ 1f
+ } else {
+ MathUtils.lerp(startAlpha, 0f, it)
+ }
+ },
+ )
+ }
+
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
new file mode 100644
index 0000000..91554e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down OCCLUDED->DOZING transition into discrete steps for corresponding views to consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludedToDozingTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION,
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.DOZING,
+ )
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 53f4488..0587826 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -60,6 +60,10 @@
private var leaveShadeOpen: Boolean = false
private var willRunDismissFromKeyguard: Boolean = false
+ /** See [BouncerToGoneFlows#showAllNotifications] */
+ val showAllNotifications: Flow<Boolean> =
+ bouncerToGoneFlows.showAllNotifications(TO_GONE_DURATION, PRIMARY_BOUNCER)
+
val notificationAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = 200.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index c033e46..b531ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -37,7 +37,7 @@
import com.android.systemui.qs.PageIndicator
import com.android.systemui.res.R
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.shared.animation.PhysicsAnimator
private const val FLING_SLOP = 1000000
private const val DISMISS_DELAY = 100L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
index b625908..b08ee16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
@@ -24,7 +24,7 @@
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import com.android.systemui.Gefingerpoken
-import com.android.wm.shell.animation.physicsAnimator
+import com.android.wm.shell.shared.animation.physicsAnimator
/**
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful when
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 1983a67..dba0173 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -53,19 +53,17 @@
private lateinit var cancelButton: TextView
private lateinit var warning: TextView
private lateinit var screenShareModeSpinner: Spinner
- private var hasCancelBeenLogged: Boolean = false
protected lateinit var dialog: AlertDialog
+ private var shouldLogCancel: Boolean = true
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
@CallSuper
override fun onStop(dialog: T) {
// onStop can be called multiple times and we only want to log once.
- if (hasCancelBeenLogged) {
- return
+ if (shouldLogCancel) {
+ mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+ shouldLogCancel = false
}
-
- mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
- hasCancelBeenLogged = true
}
@CallSuper
@@ -140,7 +138,10 @@
}
protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
- startButton.setOnClickListener(listener)
+ startButton.setOnClickListener { view ->
+ shouldLogCancel = false
+ listener?.onClick(view)
+ }
}
protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 1aef920..8d3500a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.external.QSExternalModule;
+import com.android.systemui.qs.panels.dagger.PanelsModule;
import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.di.QSTilesModule;
@@ -44,21 +45,22 @@
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
-import java.util.Map;
-
-import javax.inject.Named;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
+import java.util.Map;
+
+import javax.inject.Named;
+
/**
* Module for QS dependencies
*/
@Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class},
includes = {
MediaModule.class,
+ PanelsModule.class,
QSExternalModule.class,
QSFlagsModule.class,
QSHostModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
new file mode 100644
index 0000000..1307296
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.dagger
+
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface PanelsModule {
+ @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
new file mode 100644
index 0000000..92f87e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
+interface IconTilesRepository {
+ val iconTilesSpecs: Flow<Set<TileSpec>>
+}
+
+@SysUISingleton
+class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
+
+ /** Set of toggleable tiles that are suitable for being shown as an icon. */
+ override val iconTilesSpecs: Flow<Set<TileSpec>> =
+ flowOf(
+ setOf(
+ TileSpec.create("airplane"),
+ TileSpec.create("battery"),
+ TileSpec.create("cameratoggle"),
+ TileSpec.create("cast"),
+ TileSpec.create("color_correction"),
+ TileSpec.create("inversion"),
+ TileSpec.create("saver"),
+ TileSpec.create("dnd"),
+ TileSpec.create("flashlight"),
+ TileSpec.create("location"),
+ TileSpec.create("mictoggle"),
+ TileSpec.create("nfc"),
+ TileSpec.create("night"),
+ TileSpec.create("rotation")
+ )
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
new file mode 100644
index 0000000..367c670
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
+@SysUISingleton
+class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
+ val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 2360f27..3004485 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -620,7 +620,7 @@
showRippleEffect = false
setOnTouchListener(longPressEffect)
if (!longPressEffectViewBinder.isBound) {
- longPressEffectViewBinder.bind(this, longPressEffect)
+ longPressEffectViewBinder.bind(this, state.spec, longPressEffect)
}
} else {
// Long-press effects might have been enabled before but the new state does not
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index 15b8cfb..f34389e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -52,6 +52,7 @@
subtitleIdsMap["color_correction"] = R.array.tile_states_color_correction
subtitleIdsMap["dream"] = R.array.tile_states_dream
subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling
+ subtitleIdsMap["hearing_devices"] = R.array.tile_states_hearing_devices
}
/** Get the subtitle resource id of the given tile */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
new file mode 100644
index 0000000..81a2026
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Flags;
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
+
+import javax.inject.Inject;
+
+/** Quick settings tile: Hearing Devices **/
+public class HearingDevicesTile extends QSTileImpl<State> {
+
+ public static final String TILE_SPEC = "hearing_devices";
+
+ private final HearingDevicesDialogManager mDialogManager;
+
+ @Inject
+ public HearingDevicesTile(
+ QSHost host,
+ QsEventLogger uiEventLogger,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
+ FalsingManager falsingManager,
+ MetricsLogger metricsLogger,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter,
+ QSLogger qsLogger,
+ HearingDevicesDialogManager hearingDevicesDialogManager
+ ) {
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ statusBarStateController, activityStarter, qsLogger);
+ mDialogManager = hearingDevicesDialogManager;
+ }
+
+ @Override
+ public State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleClick(@Nullable View view) {
+ mUiHandler.post(() -> mDialogManager.showDialog(view));
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ state.label = mContext.getString(R.string.quick_settings_hearing_devices_label);
+ state.icon = ResourceIcon.get(R.drawable.qs_hearing_devices_icon);
+ }
+
+ @Nullable
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS);
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_hearing_devices_label);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return Flags.hearingAidsQsTileDialog();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 1b73225..e1b742e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -182,12 +182,19 @@
@Override
public boolean isAvailable() {
+ if (isWalletRoleAvailable()) {
+ return !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS);
+ }
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
&& !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS)
&& mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT,
UserHandle.USER_CURRENT) != null;
}
+ private boolean isWalletRoleAvailable() {
+ return mHost.getUserId() == UserHandle.USER_SYSTEM && mController.isWalletRoleAvailable();
+ }
+
@Nullable
@Override
public Intent getLongClickIntent() {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
new file mode 100644
index 0000000..a823916
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates logic regarding the occlusion state of the scene container. */
+@SysUISingleton
+class SceneContainerOcclusionInteractor
+@Inject
+constructor(
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ sceneInteractor: SceneInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) {
+ /**
+ * Whether the scene container should become invisible due to "occlusion" by an in-foreground
+ * "show when locked" activity.
+ */
+ val invisibleDueToOcclusion: Flow<Boolean> =
+ combine(
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+ sceneInteractor.transitionState,
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.AOD)
+ .onStart { emit(0f) }
+ .map { it > 0 }
+ .distinctUntilChanged(),
+ ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown ->
+ isOccludingActivityShown &&
+ !isAodFullyOrPartiallyShown &&
+ sceneTransitionState.canBeOccluded
+ }
+ .distinctUntilChanged()
+
+ private val ObservableTransitionState.canBeOccluded: Boolean
+ get() =
+ when (this) {
+ is ObservableTransitionState.Idle -> scene.canBeOccluded
+ is ObservableTransitionState.Transition ->
+ fromScene.canBeOccluded && toScene.canBeOccluded
+ }
+
+ /**
+ * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on
+ * principle not be occlude-able because they render as if they are expanding on top of the
+ * occluding activity.
+ */
+ private val SceneKey.canBeOccluded: Boolean
+ get() =
+ when (this) {
+ Scenes.Bouncer -> true
+ Scenes.Communal -> true
+ Scenes.Gone -> true
+ Scenes.Lockscreen -> true
+ Scenes.QuickSettings -> false
+ Scenes.Shade -> false
+ else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 32e8f55..0e4049b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.logger.SceneLogger
@@ -94,6 +95,7 @@
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
private val centralSurfaces: CentralSurfaces,
private val headsUpInteractor: HeadsUpNotificationInteractor,
+ private val occlusionInteractor: SceneContainerOcclusionInteractor,
) : CoreStartable {
override fun start() {
@@ -126,41 +128,40 @@
private fun hydrateVisibility() {
applicationScope.launch {
// TODO(b/296114544): Combine with some global hun state to make it visible!
- combine(
- deviceProvisioningInteractor.isDeviceProvisioned,
- deviceProvisioningInteractor.isFactoryResetProtectionActive,
- ) { isDeviceProvisioned, isFrpActive ->
- isDeviceProvisioned && !isFrpActive
- }
+ deviceProvisioningInteractor.isDeviceProvisioned
.distinctUntilChanged()
.flatMapLatest { isAllowedToBeVisible ->
if (isAllowedToBeVisible) {
- sceneInteractor.transitionState
- .mapNotNull { state ->
- when (state) {
- is ObservableTransitionState.Idle -> {
- if (state.scene != Scenes.Gone) {
- true to "scene is not Gone"
- } else {
- false to "scene is Gone"
+ combine(
+ sceneInteractor.transitionState.mapNotNull { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> {
+ if (state.scene != Scenes.Gone) {
+ true to "scene is not Gone"
+ } else {
+ false to "scene is Gone"
+ }
+ }
+ is ObservableTransitionState.Transition -> {
+ if (state.fromScene == Scenes.Gone) {
+ true to "scene transitioning away from Gone"
+ } else {
+ null
+ }
}
}
- is ObservableTransitionState.Transition -> {
- if (state.fromScene == Scenes.Gone) {
- true to "scene transitioning away from Gone"
- } else {
- null
- }
- }
- }
- }
- .combine(headsUpInteractor.isHeadsUpOrAnimatingAway) {
+ },
+ headsUpInteractor.isHeadsUpOrAnimatingAway,
+ occlusionInteractor.invisibleDueToOcclusion,
+ ) {
visibilityForTransitionState,
- isHeadsUpOrAnimatingAway ->
- if (isHeadsUpOrAnimatingAway) {
- true to "showing a HUN"
- } else {
- visibilityForTransitionState
+ isHeadsUpOrAnimatingAway,
+ invisibleDueToOcclusion,
+ ->
+ when {
+ isHeadsUpOrAnimatingAway -> true to "showing a HUN"
+ invisibleDueToOcclusion -> false to "invisible due to occlusion"
+ else -> visibilityForTransitionState
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
index 69dce83..2fbcba9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -20,9 +20,6 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,14 +33,10 @@
* Delegates calls to a runtime-provided [SceneDataSource] or to a no-op implementation if a
* delegate isn't set.
*/
-@SysUISingleton
-class SceneDataSourceDelegator
-@Inject
-constructor(
- @Application private val applicationScope: CoroutineScope,
+class SceneDataSourceDelegator(
+ applicationScope: CoroutineScope,
config: SceneContainerConfig,
) : SceneDataSource {
-
private val noOpDelegate = NoOpSceneDataSource(config.initialSceneKey)
private val delegateMutable = MutableStateFlow<SceneDataSource>(noOpDelegate)
@@ -82,6 +75,7 @@
) : SceneDataSource {
override val currentScene: StateFlow<SceneKey> =
MutableStateFlow(initialSceneKey).asStateFlow()
+
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 3081f89..922997d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -18,32 +18,11 @@
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.app.tracing.coroutines.launch
-import kotlinx.coroutines.CoroutineScope
-import java.util.function.Consumer
-import javax.inject.Inject
-
-/** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
- /**
- * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
- *
- * @param screenshot the screenshot to process
- */
- suspend fun process(screenshot: ScreenshotData): ScreenshotData
-}
/** Implementation of [ScreenshotRequestProcessor] */
-@SysUISingleton
-class RequestProcessor
-@Inject
-constructor(
+class RequestProcessor(
private val capture: ImageCapture,
private val policy: ScreenshotPolicy,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -78,24 +57,6 @@
return result
}
-
- /**
- * Note: This is for compatibility with existing Java. Prefer the suspending function when
- * calling from a Coroutine context.
- *
- * @param screenshot the screenshot to process
- * @param callback the callback to provide the processed screenshot, invoked from the main
- * thread
- */
- fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
- mainScope.launch({ "$TAG#processAsync" }) {
- val result = process(screenshot)
- callback.accept(result)
- }
- }
}
private const val TAG = "RequestProcessor"
-
-/** Exception thrown by [RequestProcessor] if something goes wrong. */
-class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b796a20..047ecb4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -29,8 +29,6 @@
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
@@ -41,7 +39,6 @@
import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
import android.app.Notification;
-import android.app.assist.AssistContent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -73,7 +70,6 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import android.window.WindowContext;
@@ -81,11 +77,11 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
@@ -218,17 +214,10 @@
// ScreenshotNotificationSmartActionsProvider.
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
static final String EXTRA_ID = "android:screenshot_id";
- static final String ACTION_TYPE_DELETE = "Delete";
- static final String ACTION_TYPE_SHARE = "Share";
- static final String ACTION_TYPE_EDIT = "Edit";
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
- static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
- static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
- static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
@@ -247,10 +236,10 @@
private final Executor mMainExecutor;
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
+ private final BroadcastDispatcher mBroadcastDispatcher;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
- private final AccessibilityManager mAccessibilityManager;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
private final ScrollCaptureClient mScrollCaptureClient;
@@ -278,7 +267,7 @@
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
private String mPackageName = "";
- private BroadcastReceiver mCopyBroadcastReceiver;
+ private final BroadcastReceiver mCopyBroadcastReceiver;
// When false, the screenshot is taken without showing the ui. Note that this only applies to
// external displays, as on the default one the UI should **always** be shown.
@@ -300,6 +289,8 @@
@AssistedInject
ScreenshotController(
Context context,
+ DisplayManager displayManager,
+ WindowManager windowManager,
FeatureFlags flags,
ScreenshotViewProxy.Factory viewProxyFactory,
ScreenshotActionsProvider.Factory actionsProviderFactory,
@@ -315,6 +306,7 @@
ActivityManager activityManager,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
+ BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ActionIntentExecutor actionExecutor,
UserManager userManager,
@@ -337,16 +329,17 @@
mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
+ mBroadcastDispatcher = broadcastDispatcher;
mScreenshotHandler = timeoutHandler;
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mDisplayId = displayId;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+ mDisplayManager = displayManager;
+ mWindowManager = windowManager;
final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
- mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
mActionExecutor = actionExecutor;
mUserManager = userManager;
@@ -363,8 +356,6 @@
mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
});
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
// Setup the window that we are going to use
mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
mWindowLayoutParams.setTitle("ScreenshotAnimation");
@@ -390,9 +381,9 @@
}
}
};
- mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
- ClipboardOverlayController.COPY_OVERLAY_ACTION),
- ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+ mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+ ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
+ Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
@@ -442,16 +433,6 @@
prepareViewForNewScreenshot(screenshot, oldPackageName);
- if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
- new AssistContentRequester.Callback() {
- @Override
- public void onAssistContentAvailable(AssistContent assistContent) {
- screenshot.setContextUrl(assistContent.getWebUri());
- }
- });
- }
-
if (!shouldShowUi()) {
saveScreenshotInWorkerThread(
screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
@@ -567,7 +548,7 @@
* Release the constructed window context.
*/
private void releaseContext() {
- mContext.unregisterReceiver(mCopyBroadcastReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver);
mContext.release();
}
@@ -615,7 +596,7 @@
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
- setContentView(mViewProxy.getView());
+ mWindow.setContentView(mViewProxy.getView());
}
private void enqueueScrollCaptureRequest(UserHandle owner) {
@@ -697,10 +678,8 @@
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getDisplay().getRealMetrics(displayMetrics);
- Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
- new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
+ Bitmap newScreenshot =
+ mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
if (newScreenshot != null) {
// delay starting scroll capture to make sure scrim is up before the app moves
@@ -797,10 +776,6 @@
}
}
- private void setContentView(View contentView) {
- mWindow.setContentView(contentView);
- }
-
@MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
@@ -912,12 +887,10 @@
public void onFinish() {
}
};
- Pair<ActivityOptions, ExitTransitionCoordinator> transition =
- ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mViewProxy.getScreenshotPreview(),
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
- return transition;
+ return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
+ Pair.create(mViewProxy.getScreenshotPreview(),
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
}
/** Reset screenshot view and then call onCompleteRunnable */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
index d874eb6..4079abe 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -105,7 +105,7 @@
/** Factory for [ScreenshotNotificationsController]. */
@AssistedFactory
- interface Factory {
- fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+ fun interface Factory {
+ fun create(displayId: Int): ScreenshotNotificationsController
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index d5ab306..c8067df 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -30,7 +30,7 @@
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
-internal class ScreenshotProxyService
+class ScreenshotProxyService
@Inject
constructor(
private val mExpansionMgr: ShadeExpansionStateManager,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
new file mode 100644
index 0000000..796457d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+/** Processes a screenshot request sent from [ScreenshotHelper]. */
+interface ScreenshotRequestProcessor {
+ /**
+ * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
+ *
+ * @param screenshot the screenshot to process
+ */
+ suspend fun process(screenshot: ScreenshotData): ScreenshotData
+}
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index cb2dba0..65e8457 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -90,7 +90,6 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.scroll.ScrollCaptureController;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -789,15 +788,8 @@
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
prepareSharedTransition();
- Intent shareIntent;
- if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
- && mScreenshotData.getContextUrl() != null) {
- shareIntent = ActionIntentCreator.INSTANCE.createShareWithText(
- imageData.uri, mScreenshotData.getContextUrl().toString());
- } else {
- shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
- imageData.uri, imageData.subject);
- }
+ Intent shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
+ imageData.uri, imageData.subject);
mCallbacks.onAction(shareIntent, imageData.owner, false);
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index bc33755..92d3e55 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,7 +5,6 @@
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.app.tracing.coroutines.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
@@ -18,6 +17,23 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+interface TakeScreenshotExecutor {
+ suspend fun executeScreenshots(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: (Uri) -> Unit,
+ requestCallback: RequestCallback
+ )
+ fun onCloseSystemDialogsReceived()
+ fun removeWindows()
+ fun onDestroy()
+ fun executeScreenshotsAsync(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: Consumer<Uri>,
+ requestCallback: RequestCallback
+ )
+}
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -26,7 +42,7 @@
* Captures a screenshot for each [Display] available.
*/
@SysUISingleton
-class TakeScreenshotExecutor
+class TakeScreenshotExecutorImpl
@Inject
constructor(
private val screenshotControllerFactory: ScreenshotController.Factory,
@@ -35,7 +51,7 @@
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
private val uiEventLogger: UiEventLogger,
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
-) {
+) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
@@ -47,7 +63,7 @@
* [onSaved] is invoked only on the default display result. [RequestCallback.onFinish] is
* invoked only when both screenshot UIs are removed.
*/
- suspend fun executeScreenshots(
+ override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
onSaved: (Uri) -> Unit,
requestCallback: RequestCallback
@@ -128,12 +144,8 @@
}
}
- /**
- * Propagates the close system dialog signal to all controllers.
- *
- * TODO(b/295143676): Move the receiver in this class once the flag is flipped.
- */
- fun onCloseSystemDialogsReceived() {
+ /** Propagates the close system dialog signal to all controllers. */
+ override fun onCloseSystemDialogsReceived() {
screenshotControllers.forEach { (_, screenshotController) ->
if (!screenshotController.isPendingSharedTransition) {
screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
@@ -142,7 +154,7 @@
}
/** Removes all screenshot related windows. */
- fun removeWindows() {
+ override fun removeWindows() {
screenshotControllers.forEach { (_, screenshotController) ->
screenshotController.removeWindow()
}
@@ -151,7 +163,7 @@
/**
* Destroys the executor. Afterwards, this class is not expected to work as intended anymore.
*/
- fun onDestroy() {
+ override fun onDestroy() {
screenshotControllers.forEach { (_, screenshotController) ->
screenshotController.onDestroy()
}
@@ -171,12 +183,12 @@
}
/** For java compatibility only. see [executeScreenshots] */
- fun executeScreenshotsAsync(
+ override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
onSaved: Consumer<Uri>,
requestCallback: RequestCallback
) {
- mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
+ mainScope.launch {
executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 9cf347b..2187b51 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,13 +21,11 @@
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
-import static com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import android.annotation.MainThread;
import android.app.Service;
@@ -50,24 +48,19 @@
import android.view.Display;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
-import javax.inject.Provider;
public class TakeScreenshotService extends Service {
private static final String TAG = logTag(TakeScreenshotService.class);
- private final ScreenshotController mScreenshot;
-
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final UiEventLogger mUiEventLogger;
@@ -75,27 +68,20 @@
private final Handler mHandler;
private final Context mContext;
private final @Background Executor mBgExecutor;
- private final RequestProcessor mProcessor;
- private final FeatureFlags mFeatureFlags;
+ private final TakeScreenshotExecutor mTakeScreenshotExecutor;
+ @SuppressWarnings("deprecation")
private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
+ if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
if (DEBUG_DISMISS) {
Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
}
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- // TODO(b/295143676): move receiver inside executor when the flag is enabled.
- mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
- } else if (!mScreenshot.isPendingSharedTransition()) {
- mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
- }
+ mTakeScreenshotExecutor.onCloseSystemDialogsReceived();
}
}
};
- private final Provider<TakeScreenshotExecutor> mTakeScreenshotExecutor;
-
/** Informs about coarse grained state of the Controller. */
public interface RequestCallback {
@@ -111,12 +97,14 @@
}
@Inject
- public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
- UserManager userManager, DevicePolicyManager devicePolicyManager,
+ public TakeScreenshotService(
+ UserManager userManager,
+ DevicePolicyManager devicePolicyManager,
UiEventLogger uiEventLogger,
ScreenshotNotificationsController.Factory notificationsControllerFactory,
- Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
- RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
+ Context context,
+ @Background Executor bgExecutor,
+ TakeScreenshotExecutor takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
Log.d(TAG, "new " + this);
}
@@ -127,15 +115,7 @@
mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
mContext = context;
mBgExecutor = bgExecutor;
- mFeatureFlags = featureFlags;
- mProcessor = processor;
mTakeScreenshotExecutor = takeScreenshotExecutor;
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mScreenshot = null;
- } else {
- mScreenshot = screenshotControllerFactory.create(
- Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
- }
}
@Override
@@ -161,11 +141,7 @@
if (DEBUG_SERVICE) {
Log.d(TAG, "onUnbind");
}
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().removeWindows();
- } else {
- mScreenshot.removeWindow();
- }
+ mTakeScreenshotExecutor.removeWindows();
unregisterReceiver(mCloseSystemDialogs);
return false;
}
@@ -173,11 +149,7 @@
@Override
public void onDestroy() {
super.onDestroy();
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().onDestroy();
- } else {
- mScreenshot.onDestroy();
- }
+ mTakeScreenshotExecutor.onDestroy();
if (DEBUG_SERVICE) {
Log.d(TAG, "onDestroy");
}
@@ -190,6 +162,7 @@
mReplyTo = replyTo;
}
+ @Override
public void reportError() {
reportUri(mReplyTo, null);
sendComplete(mReplyTo);
@@ -214,7 +187,6 @@
}
@MainThread
- @VisibleForTesting
void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
RequestCallback callback) {
// If the storage for this user is locked, we have no place to store
@@ -245,36 +217,9 @@
}
Log.d(TAG, "Processing screenshot data");
-
-
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
- return;
- }
- // TODO(b/295143676): Delete the following after the flag is released.
- try {
- ScreenshotData screenshotData = ScreenshotData.fromRequest(
- request, Display.DEFAULT_DISPLAY);
- mProcessor.processAsync(screenshotData, (data) ->
- dispatchToController(data, onSaved, callback));
-
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to process screenshot request!", e);
- logFailedRequest(request);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- }
+ mTakeScreenshotExecutor.executeScreenshotsAsync(request, onSaved, callback);
}
- // TODO(b/295143676): Delete this.
- private void dispatchToController(ScreenshotData screenshot,
- Consumer<Uri> uriConsumer, RequestCallback callback) {
- mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
- screenshot.getPackageNameString());
- Log.d(TAG, "Screenshot request: " + screenshot);
- mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
- }
private void logFailedRequest(ScreenshotRequest request) {
ComponentName topComponent = request.getTopComponent();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 2ce6d83..6ff0fda 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -26,21 +26,22 @@
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
-import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotActionsProvider;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
-import com.android.systemui.screenshot.ScreenshotProxyService;
-import com.android.systemui.screenshot.ScreenshotRequestProcessor;
import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
import com.android.systemui.screenshot.ScreenshotViewProxy;
+import com.android.systemui.screenshot.TakeScreenshotExecutor;
+import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
+import com.android.systemui.screenshot.policy.ScreenshotPolicyModule;
+import com.android.systemui.screenshot.proxy.SystemUiProxyModule;
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel;
import dagger.Binds;
@@ -52,7 +53,7 @@
/**
* Defines injectable resources for Screenshots
*/
-@Module
+@Module(includes = {ScreenshotPolicyModule.class, SystemUiProxyModule.class})
public abstract class ScreenshotModule {
@Binds
@@ -61,9 +62,9 @@
abstract Service bindTakeScreenshotService(TakeScreenshotService service);
@Binds
- @IntoMap
- @ClassKey(ScreenshotProxyService.class)
- abstract Service bindScreenshotProxyService(ScreenshotProxyService service);
+ @SysUISingleton
+ abstract TakeScreenshotExecutor bindTakeScreenshotExecutor(
+ TakeScreenshotExecutorImpl impl);
@Binds
abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
@@ -82,10 +83,6 @@
abstract Service bindAppClipsService(AppClipsService service);
@Binds
- abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor(
- RequestProcessor requestProcessor);
-
- @Binds
abstract ScreenshotSoundProvider bindScreenshotSoundProvider(
ScreenshotSoundProviderImpl screenshotSoundProviderImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
new file mode 100644
index 0000000..837a661
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.app.ActivityTaskManager.RootTaskInfo
+
+/** Information about the tasks on a display. */
+data class DisplayContentModel(
+ /** The id of the display. */
+ val displayId: Int,
+ /** Information about the current System UI state which can affect capture. */
+ val systemUiState: SystemUiState,
+ /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+ val rootTasks: List<RootTaskInfo>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt
new file mode 100644
index 0000000..38016ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ProfileType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+/** The profile type of a user. */
+enum class ProfileType {
+ /** The user is not a profile. */
+ NONE,
+ /** Private space user */
+ PRIVATE,
+ /** Managed (work) profile. */
+ WORK,
+ /** Cloned apps user */
+ CLONE,
+ /** Communal profile */
+ COMMUNAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt
new file mode 100644
index 0000000..78be6bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/SystemUiState.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+/** Information about SystemUI state relevant to screenshot policy. */
+data class SystemUiState(val shadeExpanded: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
new file mode 100644
index 0000000..9c81b32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot.data.repository
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Provides information about tasks related to a display. */
+interface DisplayContentRepository {
+ /** Provides information about the tasks and content presented on a given display. */
+ suspend fun getDisplayContent(displayId: Int): DisplayContentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
new file mode 100644
index 0000000..e9599dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepositoryImpl.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.repository
+
+import android.annotation.SuppressLint
+import android.app.ActivityTaskManager
+import android.app.IActivityTaskManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.proxy.SystemUiProxy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Implements DisplayTaskRepository using [IActivityTaskManager], along with [ProfileTypeRepository]
+ * and [SystemUiProxy].
+ */
+@SuppressLint("MissingPermission")
+class DisplayContentRepositoryImpl
+@Inject
+constructor(
+ private val atmService: IActivityTaskManager,
+ private val systemUiProxy: SystemUiProxy,
+ @Background private val background: CoroutineDispatcher,
+) : DisplayContentRepository {
+
+ override suspend fun getDisplayContent(displayId: Int): DisplayContentModel {
+ return withContext(background) {
+ val rootTasks = atmService.getAllRootTaskInfosOnDisplay(displayId)
+ toDisplayTasksModel(displayId, rootTasks)
+ }
+ }
+
+ private suspend fun toDisplayTasksModel(
+ displayId: Int,
+ rootTasks: List<ActivityTaskManager.RootTaskInfo>,
+ ): DisplayContentModel {
+ return DisplayContentModel(
+ displayId,
+ SystemUiState(systemUiProxy.isNotificationShadeExpanded()),
+ rootTasks
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt
new file mode 100644
index 0000000..2c6e4fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.repository
+
+import android.annotation.UserIdInt
+import com.android.systemui.screenshot.data.model.ProfileType
+
+/** A facility for checking user profile types. */
+fun interface ProfileTypeRepository {
+ /**
+ * Returns the profile type when [userId] refers to a profile user. If the profile type is
+ * unknown or not a profile user, [ProfileType.NONE] is returned.
+ */
+ suspend fun getProfileType(@UserIdInt userId: Int): ProfileType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt
new file mode 100644
index 0000000..42ad21bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/ProfileTypeRepositoryImpl.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:SuppressLint("MissingPermission")
+
+package com.android.systemui.screenshot.data.repository
+
+import android.annotation.SuppressLint
+import android.annotation.UserIdInt
+import android.os.UserManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.data.model.ProfileType
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+/** Fetches profile types from [UserManager] as needed, caching results for a given user. */
+class ProfileTypeRepositoryImpl
+@Inject
+constructor(
+ private val userManager: UserManager,
+ @Background private val background: CoroutineDispatcher
+) : ProfileTypeRepository {
+ /** Cache to avoid repeated requests to IActivityTaskManager for the same userId */
+ private val cache = mutableMapOf<Int, ProfileType>()
+ private val mutex = Mutex()
+
+ override suspend fun getProfileType(@UserIdInt userId: Int): ProfileType {
+ return mutex.withLock {
+ cache[userId]
+ ?: withContext(background) {
+ val userType = userManager.getUserInfo(userId).userType
+ when (userType) {
+ UserManager.USER_TYPE_PROFILE_MANAGED -> ProfileType.WORK
+ UserManager.USER_TYPE_PROFILE_PRIVATE -> ProfileType.PRIVATE
+ UserManager.USER_TYPE_PROFILE_CLONE -> ProfileType.CLONE
+ UserManager.USER_TYPE_PROFILE_COMMUNAL -> ProfileType.COMMUNAL
+ else -> ProfileType.NONE
+ }
+ }
+ .also { cache[userId] = it }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
new file mode 100644
index 0000000..bc71ab7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.RequestProcessor
+import com.android.systemui.screenshot.ScreenshotPolicy
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.data.repository.DisplayContentRepositoryImpl
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepositoryImpl
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import javax.inject.Provider
+
+@Module
+interface ScreenshotPolicyModule {
+
+ @Binds
+ @SysUISingleton
+ fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun bindScreenshotRequestProcessor(
+ imageCapture: ImageCapture,
+ policyProvider: Provider<ScreenshotPolicy>,
+ ): ScreenshotRequestProcessor {
+ return RequestProcessor(imageCapture, policyProvider.get())
+ }
+ }
+
+ @Binds
+ @SysUISingleton
+ fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt
new file mode 100644
index 0000000..e3eb3c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxy.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.proxy
+
+/**
+ * Provides a mechanism to interact with the main SystemUI process.
+ *
+ * ScreenshotService runs in an isolated process. Because of this, interactions with an outside
+ * component with shared state must be accessed through this proxy to reach the correct instance.
+ *
+ * TODO: Rename and relocate 'ScreenshotProxyService' to this package and remove duplicate clients.
+ */
+interface SystemUiProxy {
+ /** Indicate if the notification shade is "open"... (not in the fully collapsed position) */
+ suspend fun isNotificationShadeExpanded(): Boolean
+
+ /**
+ * Request keyguard dismissal, raising keyguard credential entry if required and waits for
+ * completion.
+ */
+ suspend fun dismissKeyguard()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt
new file mode 100644
index 0000000..dcf58bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyClient.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.proxy
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.screenshot.IOnDoneCallback
+import com.android.systemui.screenshot.IScreenshotProxy
+import com.android.systemui.screenshot.ScreenshotProxyService
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CompletableDeferred
+
+private const val TAG = "SystemUiProxy"
+
+/** An implementation of [SystemUiProxy] using [ScreenshotProxyService]. */
+class SystemUiProxyClient @Inject constructor(@Application context: Context) : SystemUiProxy {
+ @SuppressLint("ImplicitSamInstance")
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ override suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+ proxyConnector
+ .postForResult { it.isNotificationShadeExpanded }
+ .whenComplete { expanded, error ->
+ error?.also { Log.wtf(TAG, "isNotificationShadeExpanded", it) }
+ k.resume(expanded ?: false)
+ }
+ }
+
+ override suspend fun dismissKeyguard() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ if (proxyConnector.run { it.dismissKeyguard(onDoneBinder) }) {
+ completion.await()
+ } else {
+ Log.wtf(TAG, "Keyguard dismissal request failed")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt
new file mode 100644
index 0000000..4dd5cc4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/proxy/SystemUiProxyModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.proxy
+
+import android.app.Service
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.screenshot.ScreenshotProxyService
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SystemUiProxyModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenshotProxyService::class)
+ fun bindScreenshotProxyService(service: ScreenshotProxyService): Service
+
+ @Binds
+ @SysUISingleton
+ fun bindSystemUiProxy(systemUiProxyClient: SystemUiProxyClient): SystemUiProxy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 3169e9c..33cf9ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -26,12 +26,14 @@
import androidx.compose.ui.platform.ComposeView
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.communal.dagger.Communal
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.util.kotlin.collectFlow
@@ -52,6 +54,7 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
+ @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
private var communalContainerView: View? = null
@@ -125,6 +128,7 @@
PlatformTheme {
CommunalContainer(
viewModel = communalViewModel,
+ dataSourceDelegator = dataSourceDelegator,
dialogFactory = dialogFactory,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index c501d88..6bf5535 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -27,6 +27,8 @@
import android.view.MotionEvent;
import android.widget.FrameLayout;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+
/** The shade view. */
public final class NotificationPanelView extends FrameLayout {
static final boolean DEBUG = false;
@@ -41,14 +43,20 @@
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
- setWillNotDraw(!DEBUG);
- mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+ if (!SceneContainerFlag.isEnabled()) {
+ setWillNotDraw(!DEBUG);
+ mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
- setBackgroundColor(Color.TRANSPARENT);
+ setBackgroundColor(Color.TRANSPARENT);
+ }
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
+ if (SceneContainerFlag.isEnabled()) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ return;
+ }
if (mRtlChangeListener != null) {
mRtlChangeListener.onRtlPropertielsChanged(layoutDirection);
}
@@ -56,14 +64,19 @@
@Override
public boolean shouldDelayChildPressedState() {
+ if (SceneContainerFlag.isEnabled()) {
+ return super.shouldDelayChildPressedState();
+ }
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- if (mCurrentPanelAlpha != 255) {
- canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mAlphaPaint);
+ if (!SceneContainerFlag.isEnabled()) {
+ if (mCurrentPanelAlpha != 255) {
+ canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mAlphaPaint);
+ }
}
}
@@ -83,6 +96,9 @@
@Override
public boolean hasOverlappingRendering() {
+ if (SceneContainerFlag.isEnabled()) {
+ return super.hasOverlappingRendering();
+ }
return !mDozing;
}
@@ -102,6 +118,9 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (SceneContainerFlag.isEnabled()) {
+ return super.onInterceptTouchEvent(event);
+ }
return mTouchHandler.onInterceptTouchEvent(event);
}
@@ -113,7 +132,9 @@
@Override
public void dispatchConfigurationChanged(Configuration newConfig) {
super.dispatchConfigurationChanged(newConfig);
- mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+ if (!SceneContainerFlag.isEnabled()) {
+ mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+ }
}
/** Callback for right-to-left setting changes. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c93ef65..fa5f30f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -96,12 +96,12 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.ActiveUnlockConfig;
+import com.android.keyguard.LockIconViewController;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUnfoldTransition;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -334,6 +334,7 @@
private final ScrimController mScrimController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
+ private final ShadeHeaderController mShadeHeaderController;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -730,6 +731,7 @@
FragmentService fragmentService,
IStatusBarService statusBarService,
ContentResolver contentResolver,
+ ShadeHeaderController shadeHeaderController,
ScreenOffAnimationController screenOffAnimationController,
LockscreenGestureLogger lockscreenGestureLogger,
ShadeExpansionStateManager shadeExpansionStateManager,
@@ -874,6 +876,7 @@
mSplitShadeEnabled =
mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
mView.setWillNotDraw(!DEBUG_DRAWABLE);
+ mShadeHeaderController = shadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
@@ -979,6 +982,7 @@
});
mAlternateBouncerInteractor = alternateBouncerInteractor;
dumpManager.registerDumpable(this);
+ SceneContainerFlag.assertInLegacyMode();
}
private void unlockAnimationFinished() {
@@ -1107,6 +1111,9 @@
}
mTapAgainViewController.init();
+ mShadeHeaderController.init();
+ mShadeHeaderController.setShadeCollapseAction(
+ () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f));
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -3549,9 +3556,9 @@
}
@Override
- public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+ public void fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
mView.animate().cancel();
- return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
+ mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
endAction);
}
@@ -4115,9 +4122,10 @@
@Override
public void updateExpansionAndVisibility() {
- mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx);
-
+ if (!SceneContainerFlag.isEnabled()) {
+ mShadeExpansionStateManager.onPanelExpansionChanged(
+ mExpandedFraction, isExpanded(), isTracking());
+ }
updateVisibility();
}
@@ -4153,7 +4161,8 @@
}
/** Sends an external (e.g. Status Bar) intercept touch event to the Shade touch handler. */
- boolean handleExternalInterceptTouch(MotionEvent event) {
+ @Override
+ public boolean handleExternalInterceptTouch(MotionEvent event) {
try {
mUseExternalTouch = true;
return mTouchHandler.onInterceptTouchEvent(event);
@@ -4968,6 +4977,12 @@
return false;
}
+ if (DeviceEntryUdfpsRefactor.isEnabled()
+ && mAlternateBouncerInteractor.isVisibleState()) {
+ // never send touches to shade if the alternate bouncer is showing
+ return false;
+ }
+
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getDownTime() == mLastTouchDownTime) {
// An issue can occur when swiping down after unlock, where multiple down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 59da8f1..324dfdf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -52,6 +52,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -132,7 +133,8 @@
private DragDownHelper mDragDownHelper;
private boolean mExpandingBelowNotch;
private final DockManager mDockManager;
- private final NotificationPanelViewController mNotificationPanelViewController;
+ private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private boolean mIsTrackingBarGesture = false;
@@ -154,7 +156,8 @@
DockManager dockManager,
NotificationShadeDepthController depthController,
NotificationShadeWindowView notificationShadeWindowView,
- NotificationPanelViewController notificationPanelViewController,
+ ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
ShadeExpansionStateManager shadeExpansionStateManager,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -187,7 +190,8 @@
mStatusBarStateController = statusBarStateController;
mView = notificationShadeWindowView;
mDockManager = dockManager;
- mNotificationPanelViewController = notificationPanelViewController;
+ mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mShadeExpansionStateManager = shadeExpansionStateManager;
mDepthController = depthController;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -374,7 +378,7 @@
}
if (!mIsTrackingBarGesture && isDown
- && mNotificationPanelViewController.isFullyCollapsed()) {
+ && mPanelExpansionInteractor.isFullyCollapsed()) {
float x = ev.getRawX();
float y = ev.getRawY();
if (mStatusBarViewController.touchIsWithinView(x, y)) {
@@ -447,7 +451,7 @@
} else {
bouncerShowing = mService.isBouncerShowing();
}
- if (mNotificationPanelViewController.isFullyExpanded()
+ if (mPanelExpansionInteractor.isFullyExpanded()
&& !bouncerShowing
&& !mStatusBarStateController.isDozing()) {
if (mDragDownHelper.isDragDownEnabled()) {
@@ -503,7 +507,7 @@
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
if (!MigrateClocksToBlueprint.isEnabled()) {
- mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
+ mShadeViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
}
@@ -522,7 +526,7 @@
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
}
- if (!handled && mNotificationPanelViewController.handleExternalTouch(ev)) {
+ if (!handled && mShadeViewController.handleExternalTouch(ev)) {
return true;
}
} else {
@@ -611,7 +615,7 @@
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
- if (mNotificationPanelViewController.handleExternalInterceptTouch(ev)) {
+ if (mShadeViewController.handleExternalInterceptTouch(ev)) {
mShadeLogger.d("NSWVC: NPVC intercepted");
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 7525184..243ea68 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -965,14 +965,21 @@
}
}
- void updateQsState() {
- boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
+ private void setQsFullScreen(boolean qsFullScreen) {
mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
if (!SceneContainerFlag.isEnabled()) {
mNotificationStackScrollLayoutController.setScrollingEnabled(
mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
}
+ }
+
+ void updateQsState() {
+ if (!FooterViewRefactor.isEnabled()) {
+ // Update full screen state; note that this will be true if the QS panel is only
+ // partially expanded, and that is fixed with the footer view refactor.
+ setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled);
+ }
if (mQsStateUpdateListener != null) {
mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
@@ -1035,6 +1042,11 @@
// Update the light bar
mLightBarController.setQsExpanded(mFullyExpanded);
+
+ if (FooterViewRefactor.isEnabled()) {
+ // Update full screen state
+ setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled);
+ }
}
float getLockscreenShadeDragProgress() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 037dc4d..07836e4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -127,7 +127,9 @@
@Override
public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
- if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
+ int statusBarState = mStatusBarStateController.getState();
+ if (!force && statusBarState != StatusBarState.SHADE
+ && statusBarState != StatusBarState.SHADE_LOCKED) {
runPostCollapseActions();
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
index 71dfafa..d9c1f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionChangeEvent.kt
@@ -23,7 +23,5 @@
/** Whether the panel should be considered expanded */
val expanded: Boolean,
/** Whether the user is actively dragging the panel. */
- val tracking: Boolean,
- /** The amount of pixels that the user has dragged during the expansion. */
- val dragDownPxAmount: Float
+ val tracking: Boolean
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index df5ff5a..359ddd8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -18,13 +18,13 @@
import android.annotation.IntDef
import android.os.Trace
-import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
+import android.os.Trace.TRACE_TAG_APP as TRACE_TAG
/**
* A class responsible for managing the notification panel's current state.
@@ -42,7 +42,6 @@
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
private var tracking: Boolean = false
- private var dragDownPxAmount: Float = 0f
/**
* Adds a listener that will be notified when the panel expansion fraction has changed and
@@ -53,7 +52,7 @@
@Deprecated("Use ShadeInteractor instead")
fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
expansionListeners.add(listener)
- return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+ return ShadeExpansionChangeEvent(fraction, expanded, tracking)
}
/** Adds a listener that will be notified when the panel state has changed. */
@@ -76,8 +75,7 @@
fun onPanelExpansionChanged(
@FloatRange(from = 0.0, to = 1.0) fraction: Float,
expanded: Boolean,
- tracking: Boolean,
- dragDownPxAmount: Float
+ tracking: Boolean
) {
require(!fraction.isNaN()) { "fraction cannot be NaN" }
val oldState = state
@@ -85,7 +83,6 @@
this.fraction = fraction
this.expanded = expanded
this.tracking = tracking
- this.dragDownPxAmount = dragDownPxAmount
var fullyClosed = true
var fullyOpened = false
@@ -111,7 +108,6 @@
"f=$fraction " +
"expanded=$expanded " +
"tracking=$tracking " +
- "dragDownPxAmount=$dragDownPxAmount " +
"${if (fullyOpened) " fullyOpened" else ""} " +
if (fullyClosed) " fullyClosed" else ""
)
@@ -124,8 +120,7 @@
}
}
- val expansionChangeEvent =
- ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
+ val expansionChangeEvent = ShadeExpansionChangeEvent(fraction, expanded, tracking)
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 8d23f5d..b5b46f1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.shade
import android.view.MotionEvent
-import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
@@ -304,8 +304,7 @@
msg: String,
forceCancel: Boolean,
expand: Boolean,
- )
- {
+ ) {
buffer.log(
TAG,
LogLevel.VERBOSE,
@@ -322,8 +321,7 @@
msg: String,
panelClosedOnDown: Boolean,
expandFraction: Float,
- )
- {
+ ) {
buffer.log(
TAG,
LogLevel.VERBOSE,
@@ -381,7 +379,6 @@
shouldControlScreenOff: Boolean,
deviceInteractive: Boolean,
isPulsing: Boolean,
- isFrpActive: Boolean,
) {
buffer.log(
TAG,
@@ -392,12 +389,11 @@
bool3 = shouldControlScreenOff
bool4 = deviceInteractive
str1 = isPulsing.toString()
- str2 = isFrpActive.toString()
},
{
"CentralSurfaces updateNotificationPanelTouchState set disabled to: $bool1\n" +
"isGoingToSleep: $bool2, !shouldControlScreenOff: $bool3," +
- "!mDeviceInteractive: $bool4, !isPulsing: $str1, isFrpActive: $str2"
+ "!mDeviceInteractive: $bool4, !isPulsing: $str1"
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 2d3833c..648d4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -162,9 +162,7 @@
@Binds
@SysUISingleton
- abstract fun bindsShadeViewController(
- notificationPanelViewController: NotificationPanelViewController
- ): ShadeViewController
+ abstract fun bindsShadeViewController(shadeSurface: ShadeSurface): ShadeViewController
@Binds
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index d02c215..7346a28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.shade
-import android.view.ViewPropertyAnimator
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
@@ -48,7 +47,7 @@
fun cancelAnimation()
/** Animates the view from its current alpha to zero then runs the runnable. */
- fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+ fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable)
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
new file mode 100644
index 0000000..adb2928
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import javax.inject.Inject
+
+class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewControllerEmptyImpl() {
+ override fun initDependencies(
+ centralSurfaces: CentralSurfaces,
+ recorder: GestureRecorder,
+ hideExpandedRunnable: Runnable,
+ headsUpManager: HeadsUpManager
+ ) {}
+
+ override fun cancelPendingCollapse() {
+ // Do nothing
+ }
+
+ override fun cancelAnimation() {
+ // Do nothing
+ }
+
+ override fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable) {
+ // Do nothing
+ }
+
+ override fun setBouncerShowing(bouncerShowing: Boolean) {
+ // Do nothing
+ }
+
+ override fun setTouchAndAnimationDisabled(disabled: Boolean) {
+ // TODO(b/322197941): determine if still needed
+ }
+
+ override fun setWillPlayDelayedDozeAmountAnimation(willPlay: Boolean) {
+ // TODO(b/322494538): determine if still needed
+ }
+
+ override fun setDozing(dozing: Boolean, animate: Boolean) {
+ // Do nothing
+ }
+
+ override fun setImportantForAccessibility(mode: Int) {
+ // Do nothing
+ }
+
+ override fun resetTranslation() {
+ // Do nothing
+ }
+
+ override fun resetAlpha() {
+ // Do nothing
+ }
+
+ override fun onScreenTurningOn() {
+ // Do nothing
+ }
+
+ override fun onThemeChanged() {
+ // Do nothing
+ }
+
+ override fun updateExpansionAndVisibility() {
+ // Do nothing
+ }
+
+ override fun updateResources() {
+ // Do nothing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 5b2377f..4e1edd3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -33,14 +33,12 @@
/** Returns whether the shade's top level view is enabled. */
@Deprecated("No longer supported. Do not add new calls to this.") val isViewEnabled: Boolean
- /** Returns whether status bar icons should be hidden when the shade is expanded. */
- fun shouldHideStatusBarIconsWhenExpanded(): Boolean
-
/** If the latency tracker is enabled, begins tracking expand latency. */
@Deprecated("No longer supported. Do not add new calls to this.")
fun startExpandLatencyTracking()
/** Sets the alpha value of the shade to a value between 0 and 255. */
+ @Deprecated("No longer supported. Do not add new calls to this.")
fun setAlpha(alpha: Int, animate: Boolean)
/**
@@ -48,6 +46,7 @@
*
* @see .setAlpha
*/
+ @Deprecated("No longer supported. Do not add new calls to this.")
fun setAlphaChangeAnimationEndAction(r: Runnable)
/** Sets Qs ScrimEnabled and updates QS state. */
@@ -61,7 +60,7 @@
@Deprecated("Does nothing when scene container is enabled.") fun updateSystemUiStateFlags()
/** Ensures that the touchable region is updated. */
- fun updateTouchableRegion()
+ @Deprecated("No longer supported. Do not add new calls to this.") fun updateTouchableRegion()
/**
* Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
@@ -72,6 +71,8 @@
*/
fun handleExternalTouch(event: MotionEvent): Boolean
+ fun handleExternalInterceptTouch(event: MotionEvent): Boolean
+
/**
* Triggered when an input focus transfer gesture has started.
*
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index e037c70..0c41efd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -28,7 +28,7 @@
import kotlinx.coroutines.flow.flowOf
/** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() :
+open class ShadeViewControllerEmptyImpl @Inject constructor() :
ShadeViewController,
ShadeBackActionInteractor,
ShadeLockscreenInteractor,
@@ -81,6 +81,10 @@
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
+ override fun handleExternalInterceptTouch(event: MotionEvent): Boolean {
+ return false
+ }
+
override fun startInputFocusTransfer() {}
override fun cancelInputFocusTransfer() {}
override fun finishInputFocusTransfer(velocity: Float) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index 6611303..dfdf2ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -72,4 +72,8 @@
/** Returns the StatusBarState. Note: System UI was formerly known simply as Status Bar. */
@Deprecated("Use SceneInteractor or ShadeInteractor instead") val barState: Int
+
+ /** Returns whether status bar icons should be hidden when the shade is expanded. */
+ @Deprecated("No longer supported. Do not add new calls to this.")
+ fun shouldHideStatusBarIconsWhenExpanded(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 561d0bc..58bcd2e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -69,27 +69,21 @@
state.fromScene == Scenes.Gone ->
if (state.toScene.isExpandable()) {
// Moving from Gone to a scene that can animate-expand has a
- // panel
- // expansion
- // that tracks with the transition.
+ // panel expansion that tracks with the transition.
state.progress
} else {
// Moving from Gone to a scene that doesn't animate-expand
- // immediately makes
- // the panel fully expanded.
+ // immediately makes the panel fully expanded.
flowOf(1f)
}
state.toScene == Scenes.Gone ->
if (state.fromScene.isExpandable()) {
// Moving to Gone from a scene that can animate-expand has a
- // panel
- // expansion
- // that tracks with the transition.
+ // panel expansion that tracks with the transition.
state.progress.map { 1 - it }
} else {
// Moving to Gone from a scene that doesn't animate-expand
- // immediately makes
- // the panel fully collapsed.
+ // immediately makes the panel fully collapsed.
flowOf(0f)
}
else -> flowOf(1f)
@@ -126,6 +120,15 @@
override val barState
get() = statusBarStateController.state
+ @Deprecated("No longer supported. Do not add new calls to this.")
+ override fun shouldHideStatusBarIconsWhenExpanded(): Boolean {
+ if (shadeAnimationInteractor.isLaunchingActivity.value) {
+ return false
+ }
+ // TODO(b/325936094) if a HUN is showing, return false
+ return sceneInteractor.currentScene.value == Scenes.Lockscreen
+ }
+
private fun SceneKey.isExpandable(): Boolean {
return this == Scenes.Shade || this == Scenes.QuickSettings
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index cde45f2..0de3c10 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,6 +29,9 @@
/** Emits true if the shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean>
+ /** Emits true if QS is currently allowed and false otherwise. */
+ val isQsEnabled: StateFlow<Boolean>
+
/** Whether either the shade or QS is fully expanded. */
val isAnyFullyExpanded: StateFlow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 5fbd2cf..883ef97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -29,6 +29,7 @@
private val inactiveFlowBoolean = MutableStateFlow(false)
private val inactiveFlowFloat = MutableStateFlow(0f)
override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
+ override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean
override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat
override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index e619806..d68e28c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -28,7 +28,6 @@
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
-import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -56,15 +55,16 @@
private val baseShadeInteractor: BaseShadeInteractor,
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
- combine(
- deviceProvisioningInteractor.isFactoryResetProtectionActive,
- disableFlagsRepository.disableFlags,
- ) { isFrpActive, isDisabledByFlags ->
- isDisabledByFlags.isShadeEnabled() && !isFrpActive
- }
+ disableFlagsRepository.disableFlags
+ .map { isDisabledByFlags -> isDisabledByFlags.isShadeEnabled() }
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+ override val isQsEnabled: StateFlow<Boolean> =
+ disableFlagsRepository.disableFlags
+ .map { it.isQuickSettingsEnabled() }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
override val isAnyFullyExpanded: StateFlow<Boolean> =
anyExpansion
.map { it >= 1f }
@@ -84,11 +84,8 @@
powerInteractor.isAsleep,
keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningInteractor.isFactoryResetProtectionActive,
- ) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
+ ) { isAsleep, goingToSleep, isPulsing ->
when {
- // Touches are disabled when Factory Reset Protection is active
- isFrpActive -> false
// If the device is going to sleep, only accept touches if we're still
// animating
goingToSleep -> dozeParams.shouldControlScreenOff()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index ac881b5..7d46d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -66,7 +66,8 @@
when (statusBarState) {
// legacyShadeExpansion is 1 instead of 0 when QS is expanded
StatusBarState.SHADE ->
- if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
+ if (!splitShadeEnabled && qsExpansion > 0f) 1f - qsExpansion
+ else legacyShadeExpansion
StatusBarState.KEYGUARD -> lockscreenShadeExpansion
// dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
// the pointer is lifted and the lockscreen shade is fully expanded
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 60810a0..f3802da 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -23,15 +23,20 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeHeaderController
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -45,23 +50,32 @@
@ShadeTouchLog private val touchLog: LogBuffer,
private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
- private val controller: SplitShadeStateController,
- private val shadeController: ShadeController,
- private val shadeHeaderController: ShadeHeaderController,
+ private val splitShadeStateController: SplitShadeStateController,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
+ private val sceneInteractorProvider: Provider<SceneInteractor>,
+ private val panelExpansionInteractorProvider: Provider<PanelExpansionInteractor>,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
) : CoreStartable {
override fun start() {
hydrateShadeMode()
+ hydrateShadeExpansionStateManager()
logTouchesTo(touchLog)
- initHeaderController()
scrimShadeTransitionController.init()
}
- private fun initHeaderController() {
- shadeHeaderController.init()
- shadeHeaderController.shadeCollapseAction = Runnable {
- shadeController.animateCollapseShade()
+ private fun hydrateShadeExpansionStateManager() {
+ if (SceneContainerFlag.isEnabled) {
+ combine(
+ panelExpansionInteractorProvider.get().legacyPanelExpansion,
+ sceneInteractorProvider.get().isTransitionUserInputOngoing,
+ ) { panelExpansion, tracking ->
+ shadeExpansionStateManager.onPanelExpansionChanged(
+ fraction = panelExpansion,
+ expanded = panelExpansion > 0f,
+ tracking = tracking,
+ )
+ }.launchIn(applicationScope)
}
}
@@ -71,7 +85,9 @@
// Force initial collection.
.onStart { emit(Unit) }
.map { applicationContext.resources }
- .map { resources -> controller.shouldUseSplitNotificationShade(resources) }
+ .map { resources ->
+ splitShadeStateController.shouldUseSplitNotificationShade(resources)
+ }
.collect { isSplitShade ->
shadeRepository.setShadeMode(
if (isSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 151e289..e38e53d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -17,28 +17,20 @@
package com.android.systemui.shade.transition
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionStateManager
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.phone.ScrimController
-import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Controls the scrim properties during the shade expansion transition on non-lockscreen. */
@SysUISingleton
class ScrimShadeTransitionController
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
- private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
private val dumpManager: DumpManager,
private val scrimController: ScrimController,
) {
@@ -47,32 +39,17 @@
private var currentPanelState: Int? = null
fun init() {
- if (SceneContainerFlag.isEnabled) {
- applicationScope.launch {
- panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
- onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = panelExpansion,
- expanded = panelExpansion > 0f,
- tracking = true,
- dragDownPxAmount = 0f,
- )
- )
- }
- }
- } else {
- val currentState =
- shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
- onPanelExpansionChanged(currentState)
- shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
- }
+ val currentState =
+ shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+ onPanelExpansionChanged(currentState)
+ shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
dumpManager.registerDumpable(
ScrimShadeTransitionController::class.java.simpleName,
this::dump
)
}
- fun onPanelStateChanged(@PanelState state: Int) {
+ private fun onPanelStateChanged(@PanelState state: Int) {
currentPanelState = state
onStateChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 1191c0f..72a9c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.util.Date
@@ -53,6 +54,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
context: Context,
+ shadeInteractor: ShadeInteractor,
mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
@@ -85,6 +87,12 @@
/** Whether or not the privacy chip is enabled in the device privacy config. */
val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
+ /** Whether or not the Shade Header should be disabled based on disableFlags. */
+ val isDisabled: StateFlow<Boolean> =
+ shadeInteractor.isQsEnabled
+ .map { !it }
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 0715dfc..8d9fab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -117,9 +117,9 @@
mShowingEntry = entry;
if (mShowingEntry != null) {
- CharSequence text = entry.headsUpStatusBarText;
- if (entry.isSensitive()) {
- text = entry.headsUpStatusBarTextPublic;
+ CharSequence text = entry.getHeadsUpStatusBarText().getValue();
+ if (entry.isSensitive().getValue()) {
+ text = entry.getHeadsUpStatusBarTextPublic().getValue();
}
mTextView.setText(text);
mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index fc1dc62..519d719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -396,7 +396,7 @@
}
if (view is ExpandableNotificationRow) {
// Only drag down on sensitive views, otherwise the ExpandHelper will take this
- return view.entry.isSensitive
+ return view.entry.isSensitive.value
}
}
return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 5f0b298..307e702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -41,7 +41,6 @@
import android.view.ViewParent;
import android.widget.RemoteViews;
import android.widget.RemoteViews.InteractionHandler;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -473,25 +472,7 @@
// if we still didn't find a view that is attached, let's abort.
return false;
}
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
- riv.getController().setRevealParams(new RemoteInputView.RevealParams(cx, cy, r));
riv.getController().setPendingIntent(pendingIntent);
riv.getController().setRemoteInput(input);
riv.getController().setRemoteInputs(inputs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 8d7fc98..acb5339 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -19,8 +19,6 @@
import android.util.AttributeSet;
import android.widget.TextView;
-import com.android.settingslib.WirelessUtils;
-
/** Shows the operator name */
public class OperatorNameView extends TextView {
private boolean mDemoMode;
@@ -41,13 +39,14 @@
mDemoMode = demoMode;
}
- void update(boolean showOperatorName,
+ void update(
+ boolean showOperatorName,
boolean hasMobile,
+ boolean airplaneMode,
OperatorNameViewController.SubInfo sub
) {
setVisibility(showOperatorName ? VISIBLE : GONE);
- boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
if (!hasMobile || airplaneMode) {
setText(null);
setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index 8afc72f..6e7d8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -16,11 +16,9 @@
package com.android.systemui.statusbar;
-import android.annotation.NonNull;
import android.os.Bundle;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.view.View;
@@ -28,47 +26,60 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor;
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import javax.inject.Inject;
+import kotlinx.coroutines.Job;
+
/** Controller for {@link OperatorNameView}. */
public class OperatorNameViewController extends ViewController<OperatorNameView> {
private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
private final DarkIconDispatcher mDarkIconDispatcher;
- private final NetworkController mNetworkController;
private final TunerService mTunerService;
private final TelephonyManager mTelephonyManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final CarrierConfigTracker mCarrierConfigTracker;
+ private final AirplaneModeInteractor mAirplaneModeInteractor;
+ private final SubscriptionManagerProxy mSubscriptionManagerProxy;
+ private final JavaAdapter mJavaAdapter;
+
+ private Job mAirplaneModeJob;
private OperatorNameViewController(OperatorNameView view,
DarkIconDispatcher darkIconDispatcher,
- NetworkController networkController,
TunerService tunerService,
TelephonyManager telephonyManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- CarrierConfigTracker carrierConfigTracker) {
+ CarrierConfigTracker carrierConfigTracker,
+ AirplaneModeInteractor airplaneModeInteractor,
+ SubscriptionManagerProxy subscriptionManagerProxy,
+ JavaAdapter javaAdapter) {
super(view);
mDarkIconDispatcher = darkIconDispatcher;
- mNetworkController = networkController;
mTunerService = tunerService;
mTelephonyManager = telephonyManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mCarrierConfigTracker = carrierConfigTracker;
+ mAirplaneModeInteractor = airplaneModeInteractor;
+ mSubscriptionManagerProxy = subscriptionManagerProxy;
+ mJavaAdapter = javaAdapter;
}
@Override
protected void onViewAttached() {
mDarkIconDispatcher.addDarkReceiver(mDarkReceiver);
- mNetworkController.addCallback(mSignalCallback);
+ mAirplaneModeJob =
+ mJavaAdapter.alwaysCollectFlow(
+ mAirplaneModeInteractor.isAirplaneMode(),
+ (isAirplaneMode) -> update());
mTunerService.addTunable(mTunable, KEY_SHOW_OPERATOR_NAME);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
}
@@ -76,7 +87,7 @@
@Override
protected void onViewDetached() {
mDarkIconDispatcher.removeDarkReceiver(mDarkReceiver);
- mNetworkController.removeCallback(mSignalCallback);
+ mAirplaneModeJob.cancel(null);
mTunerService.removeTunable(mTunable);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
}
@@ -87,11 +98,17 @@
mCarrierConfigTracker
.getShowOperatorNameInStatusBarConfig(defaultSubInfo.getSubId())
&& (mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0);
- mView.update(showOperatorName, mTelephonyManager.isDataCapable(), getDefaultSubInfo());
+ mView.update(
+ showOperatorName,
+ mTelephonyManager.isDataCapable(),
+ mAirplaneModeInteractor.isAirplaneMode().getValue(),
+ getDefaultSubInfo()
+ );
}
private SubInfo getDefaultSubInfo() {
- int defaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+ int defaultSubId = mSubscriptionManagerProxy.getDefaultDataSubscriptionId();
+
SubscriptionInfo sI = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(defaultSubId);
return new SubInfo(
sI.getSubscriptionId(),
@@ -103,36 +120,44 @@
/** Factory for constructing an {@link OperatorNameViewController}. */
public static class Factory {
private final DarkIconDispatcher mDarkIconDispatcher;
- private final NetworkController mNetworkController;
private final TunerService mTunerService;
private final TelephonyManager mTelephonyManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final CarrierConfigTracker mCarrierConfigTracker;
+ private final AirplaneModeInteractor mAirplaneModeInteractor;
+ private final SubscriptionManagerProxy mSubscriptionManagerProxy;
+ private final JavaAdapter mJavaAdapter;
@Inject
public Factory(DarkIconDispatcher darkIconDispatcher,
- NetworkController networkController,
TunerService tunerService,
TelephonyManager telephonyManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- CarrierConfigTracker carrierConfigTracker) {
+ CarrierConfigTracker carrierConfigTracker,
+ AirplaneModeInteractor airplaneModeInteractor,
+ SubscriptionManagerProxy subscriptionManagerProxy,
+ JavaAdapter javaAdapter) {
mDarkIconDispatcher = darkIconDispatcher;
- mNetworkController = networkController;
mTunerService = tunerService;
mTelephonyManager = telephonyManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mCarrierConfigTracker = carrierConfigTracker;
+ mAirplaneModeInteractor = airplaneModeInteractor;
+ mSubscriptionManagerProxy = subscriptionManagerProxy;
+ mJavaAdapter = javaAdapter;
}
/** Create an {@link OperatorNameViewController}. */
public OperatorNameViewController create(OperatorNameView view) {
return new OperatorNameViewController(view,
mDarkIconDispatcher,
- mNetworkController,
mTunerService,
mTelephonyManager,
mKeyguardUpdateMonitor,
- mCarrierConfigTracker);
+ mCarrierConfigTracker,
+ mAirplaneModeInteractor,
+ mSubscriptionManagerProxy,
+ mJavaAdapter);
}
}
@@ -149,13 +174,6 @@
(area, darkIntensity, tint) ->
mView.setTextColor(DarkIconDispatcher.getTint(area, mView, tint));
- private final SignalCallback mSignalCallback = new SignalCallback() {
- @Override
- public void setIsAirplaneMode(@NonNull IconState icon) {
- update();
- }
- };
-
private final TunerService.Tunable mTunable = (key, newValue) -> update();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 80c3551..321b608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -71,6 +71,15 @@
@NonNull
public StatusBarNotification rebuildForCanceledSmartReplies(
NotificationEntry entry) {
+ return rebuildWithExistingReplies(entry);
+ }
+
+ /**
+ * Rebuilds to include any previously-added remote input replies.
+ * For when the app cancels a notification that has already been lifetime extended.
+ */
+ @NonNull
+ public StatusBarNotification rebuildWithExistingReplies(NotificationEntry entry) {
return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */,
false /* showSpinner */, null /* mimeType */, null /* uri */);
}
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 f960fca..e5b6497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -35,9 +35,11 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeSurface;
+import com.android.systemui.shade.ShadeSurfaceImpl;
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -59,6 +61,8 @@
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import javax.inject.Provider;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -178,9 +182,20 @@
* The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
* {@link com.android.systemui.shade.ShadeModule} so others can access it.
*/
- @Binds
+ @Provides
@SysUISingleton
- ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+ static ShadeSurface provideShadeSurface(
+ SceneContainerFlags sceneContainerFlags,
+ Provider<ShadeSurfaceImpl> sceneContainerOn,
+ Provider<NotificationPanelViewController> sceneContainerOff) {
+ if (sceneContainerFlags.isEnabled()) {
+ return sceneContainerOn.get();
+ } else {
+ return sceneContainerOff.get();
+ }
+
+ }
+
/** */
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 0fd0555..c29a64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -36,6 +36,7 @@
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
@@ -45,6 +46,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -95,6 +97,7 @@
private val deviceProvisionedController: DeviceProvisionedController,
private val bypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
private val dumpManager: DumpManager,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@@ -123,7 +126,7 @@
private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList()
// Smartspace can be used on multiple displays, such as when the user casts their screen
- private var smartspaceViews = mutableSetOf<SmartspaceView>()
+ @VisibleForTesting var smartspaceViews = mutableSetOf<SmartspaceView>()
private var regionSamplers =
mutableMapOf<SmartspaceView, RegionSampler>()
@@ -272,6 +275,18 @@
}
}
+ // TODO(b/331451011): Refactor to viewmodel and use interactor pattern.
+ private val wakefulnessLifecycleObserver =
+ object : WakefulnessLifecycle.Observer {
+ override fun onStartedWakingUp() {
+ smartspaceViews.forEach { it.setScreenOn(true) }
+ }
+
+ override fun onFinishedGoingToSleep() {
+ smartspaceViews.forEach { it.setScreenOn(false) }
+ }
+ }
+
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
dumpManager.registerDumpable(this)
@@ -451,6 +466,7 @@
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
+ wakefulnessLifecycle.addObserver(wakefulnessLifecycleObserver)
datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -493,6 +509,7 @@
configurationController.removeCallback(configChangeListener)
statusBarStateController.removeCallback(statusBarStateListener)
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
+ wakefulnessLifecycle.removeObserver(wakefulnessLifecycleObserver)
session = null
datePlugin?.registerSmartspaceEventNotifier(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 7c71864..4c66f66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -23,7 +23,9 @@
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
import com.android.systemui.Dumpable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -47,11 +49,14 @@
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
@SysUISingleton
class NotificationWakeUpCoordinator
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
@@ -60,6 +65,7 @@
private val screenOffAnimationController: ScreenOffAnimationController,
private val logger: NotificationWakeUpCoordinatorLogger,
private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
+ private val communalInteractor: CommunalInteractor,
) :
OnHeadsUpChangedListener,
StatusBarStateController.StateListener,
@@ -201,6 +207,13 @@
}
}
)
+ applicationScope.launch {
+ communalInteractor.isIdleOnCommunal.collect {
+ if (!overrideDozeAmountIfCommunalShowing()) {
+ maybeClearHardDozeAmountOverrideHidingNotifs()
+ }
+ }
+ }
}
fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
@@ -302,6 +315,10 @@
return
}
+ if (overrideDozeAmountIfCommunalShowing()) {
+ return
+ }
+
if (clearHardDozeAmountOverride()) {
return
}
@@ -311,9 +328,12 @@
private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
logger.logSetDozeAmountOverride(dozing = dozing, source = source)
+ val previousOverride = hardDozeAmountOverride
hardDozeAmountOverride = if (dozing) 1f else 0f
hardDozeAmountOverrideSource = source
- updateDozeAmount()
+ if (previousOverride != hardDozeAmountOverride) {
+ updateDozeAmount()
+ }
}
private fun clearHardDozeAmountOverride(): Boolean {
@@ -434,6 +454,11 @@
return
}
+ if (overrideDozeAmountIfCommunalShowing()) {
+ this.state = newState
+ return
+ }
+
maybeClearHardDozeAmountOverrideHidingNotifs()
this.state = newState
@@ -471,6 +496,18 @@
return false
}
+ private fun overrideDozeAmountIfCommunalShowing(): Boolean {
+ if (communalInteractor.isIdleOnCommunal.value) {
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ setHardDozeAmountOverride(dozing = true, source = "Override: communal (keyguard)")
+ } else {
+ setHardDozeAmountOverride(dozing = false, source = "Override: communal (shade)")
+ }
+ return true
+ }
+ return false
+ }
+
/**
* If the last [setDozeAmount] call was an override to hide notifications, then this call will
* check for the set of states that may have caused that override, and if none of them still
@@ -483,20 +520,23 @@
val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
val dozing = statusBarStateController.isDozing
val bypass = bypassController.bypassEnabled
+ val idleOnCommunal = communalInteractor.isIdleOnCommunal.value
val animating =
screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
- // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
- // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
- // clear the override if both those conditions are cleared. But also require either
+ // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff],
+ // [overrideDozeAmountIfBypass] and [overrideDozeAmountIfCommunalShowing] based on
+ // 'animating', 'bypass' and 'idleOnCommunal' respectively, so only clear the override
+ // if all of those conditions are cleared. But also require either
// !dozing or !onKeyguard because those conditions should indicate that we intend
// notifications to be visible, and thus it is safe to unhide them.
- val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
+ val willRemove = (!onKeyguard || !dozing) && !bypass && !animating && !idleOnCommunal
logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
willRemove = willRemove,
onKeyguard = onKeyguard,
dozing = dozing,
bypass = bypass,
animating = animating,
+ idleOnCommunal = idleOnCommunal,
)
if (willRemove) {
clearHardDozeAmountOverride()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index 502e1d9..9619bea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -95,6 +95,7 @@
onKeyguard: Boolean,
dozing: Boolean,
bypass: Boolean,
+ idleOnCommunal: Boolean,
animating: Boolean,
) {
buffer.log(
@@ -103,7 +104,7 @@
{
str1 =
"willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
- " bypass=$bypass animating=$animating"
+ " bypass=$bypass animating=$animating idleOnCommunal=$idleOnCommunal"
},
{ "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
)
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 c1dd992..9ce38db 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
@@ -76,6 +76,10 @@
import java.util.List;
import java.util.Objects;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
/**
* Represents a notification that the system UI knows about
*
@@ -150,8 +154,11 @@
public CharSequence remoteInputTextWhenReset;
public long lastRemoteInputSent = NOT_LAUNCHED_YET;
public final ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
- public CharSequence headsUpStatusBarText;
- public CharSequence headsUpStatusBarTextPublic;
+
+ private final MutableStateFlow<CharSequence> mHeadsUpStatusBarText =
+ StateFlowKt.MutableStateFlow(null);
+ private final MutableStateFlow<CharSequence> mHeadsUpStatusBarTextPublic =
+ StateFlowKt.MutableStateFlow(null);
// indicates when this entry's view was first attached to a window
// this value will reset when the view is completely removed from the shade (ie: filtered out)
@@ -162,8 +169,8 @@
*/
private boolean hasSentReply;
- private boolean mSensitive = true;
- private ListenerSet<OnSensitivityChangedListener> mOnSensitivityChangedListeners =
+ private final MutableStateFlow<Boolean> mSensitive = StateFlowKt.MutableStateFlow(true);
+ private final ListenerSet<OnSensitivityChangedListener> mOnSensitivityChangedListeners =
new ListenerSet<>();
private boolean mPulseSupressed;
@@ -934,6 +941,11 @@
return Objects.equals(n.category, category);
}
+ /** @see #setSensitive(boolean, boolean) */
+ public StateFlow<Boolean> isSensitive() {
+ return mSensitive;
+ }
+
/**
* Set this notification to be sensitive.
*
@@ -942,8 +954,8 @@
*/
public void setSensitive(boolean sensitive, boolean deviceSensitive) {
getRow().setSensitive(sensitive, deviceSensitive);
- if (sensitive != mSensitive) {
- mSensitive = sensitive;
+ if (sensitive != mSensitive.getValue()) {
+ mSensitive.setValue(sensitive);
for (NotificationEntry.OnSensitivityChangedListener listener :
mOnSensitivityChangedListeners) {
listener.onSensitivityChanged(this);
@@ -951,10 +963,6 @@
}
}
- public boolean isSensitive() {
- return mSensitive;
- }
-
/** Add a listener to be notified when the entry's sensitivity changes. */
public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
mOnSensitivityChangedListeners.addIfAbsent(listener);
@@ -965,6 +973,32 @@
mOnSensitivityChangedListeners.remove(listener);
}
+ /** @see #setHeadsUpStatusBarText(CharSequence) */
+ public StateFlow<CharSequence> getHeadsUpStatusBarText() {
+ return mHeadsUpStatusBarText;
+ }
+
+ /**
+ * Sets the text to be displayed on the StatusBar, when this notification is the top pinned
+ * heads up.
+ */
+ public void setHeadsUpStatusBarText(CharSequence headsUpStatusBarText) {
+ this.mHeadsUpStatusBarText.setValue(headsUpStatusBarText);
+ }
+
+ /** @see #setHeadsUpStatusBarTextPublic(CharSequence) */
+ public StateFlow<CharSequence> getHeadsUpStatusBarTextPublic() {
+ return mHeadsUpStatusBarTextPublic;
+ }
+
+ /**
+ * Sets the text to be displayed on the StatusBar, when this notification is the top pinned
+ * heads up, and its content is sensitive right now.
+ */
+ public void setHeadsUpStatusBarTextPublic(CharSequence headsUpStatusBarTextPublic) {
+ this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarTextPublic);
+ }
+
public boolean isPulseSuppressed() {
return mPulseSupressed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 28fff15..fe59d73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -127,6 +127,15 @@
mSmartReplyController.stopSending(entry)
mNotifUpdater.onInternalNotificationUpdate(newSbn,
"Extending lifetime of notification with smart reply")
+ } else {
+ // The app may have re-cancelled a notification after it had already
+ // been lifetime extended.
+ // Rebuild the notification with the replies it already had to ensure
+ // those replies continue to be displayed.
+ val newSbn = mRebuilder.rebuildWithExistingReplies(entry)
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification that has already been " +
+ "lifetime extended.")
}
} else {
// Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a900e45..4ebb699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -39,13 +39,13 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import java.util.concurrent.ConcurrentHashMap
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
/**
* Inflates and updates icons associated with notifications
@@ -206,7 +206,7 @@
private fun getIconDescriptors(entry: NotificationEntry): Pair<StatusBarIcon, StatusBarIcon> {
val iconDescriptor = getIconDescriptor(entry, redact = false)
val sensitiveDescriptor =
- if (entry.isSensitive) {
+ if (entry.isSensitive.value) {
getIconDescriptor(entry, redact = true)
} else {
iconDescriptor
@@ -376,7 +376,7 @@
val isSmallIcon = iconDescriptor.icon.equals(entry.sbn.notification.smallIcon)
return isImportantConversation(entry) &&
!isSmallIcon &&
- (!usedInSensitiveContext || !entry.isSensitive)
+ (!usedInSensitiveContext || !entry.isSensitive.value)
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index ded635c..31e69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -988,8 +988,8 @@
}
}
- entry.headsUpStatusBarText = result.headsUpStatusBarText;
- entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ entry.setHeadsUpStatusBarText(result.headsUpStatusBarText);
+ entry.setHeadsUpStatusBarTextPublic(result.headsUpStatusBarTextPublic);
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
if (endListener != null) {
endListener.onAsyncInflationFinished(entry);
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 3944c3a..3367dc4 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
@@ -4734,6 +4734,11 @@
}
public void setQsFullScreen(boolean qsFullScreen) {
+ if (FooterViewRefactor.isEnabled()) {
+ if (qsFullScreen == mQsFullScreen) {
+ return; // no change
+ }
+ }
mQsFullScreen = qsFullScreen;
updateAlgorithmLayoutMinHeight();
updateScrollability();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5a7433d..5ab5857 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -123,7 +123,10 @@
// When the shade is closed, the footer is still present in the list, but not visible.
// This prevents the footer from being shown when a HUN is present, while still allowing
// the footer to be counted as part of the shade for measurements.
- shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged()
+ shadeInteractor.shadeExpansion
+ .map { it == 0f }
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
}
}
@@ -274,5 +277,6 @@
// TODO(b/325936094) use it for the text displayed in the StatusBar
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowViewModel =
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
+
fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f767b99..9f57606 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
@@ -61,6 +62,8 @@
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import javax.inject.Inject
@@ -79,7 +82,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.isActive
@@ -123,6 +125,7 @@
) : FlowDumperImpl(dumpManager) {
private val statesForConstrainedNotifications: Set<KeyguardState> =
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
+ private val statesForHiddenKeyguard: Set<KeyguardState> = setOf(GONE, OCCLUDED)
/**
* Is either shade/qs expanded? This intentionally does not use the [ShadeInteractor] version,
@@ -194,8 +197,12 @@
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
- .shareIn(scope = applicationScope, started = SharingStarted.Eagerly)
- .dumpWhileCollecting("isOnLockscreen")
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false
+ )
+ .dumpValue("isOnLockscreen")
/** Are we purely on the keyguard without the shade/qs? */
val isOnLockscreenWithoutShade: Flow<Boolean> =
@@ -385,37 +392,60 @@
.onStart { emit(1f) }
.dumpWhileCollecting("alphaForShadeAndQsExpansion")
- private val isGoneTransitionRunning: Flow<Boolean> =
+ private fun toFlowArray(
+ states: Set<KeyguardState>,
+ flow: (KeyguardState) -> Flow<Boolean>
+ ): Array<Flow<Boolean>> {
+ return states.map { flow(it) }.toTypedArray()
+ }
+
+ private val isTransitioningToHiddenKeyguard: Flow<Boolean> =
flow {
while (currentCoroutineContext().isActive) {
emit(false)
- // Ensure start where GONE is inactive
- keyguardTransitionInteractor.transitionValue(GONE).first { it == 0f }
- // Wait for a GONE transition to begin
- keyguardTransitionInteractor.transitionStepsToState(GONE).first {
- it.value > 0f && it.transitionState == RUNNING
- }
+ // Ensure states are inactive to start
+ and(
+ *toFlowArray(statesForHiddenKeyguard) { state ->
+ keyguardTransitionInteractor.transitionValue(state).map { it == 0f }
+ }
+ )
+ .first { it }
+ // Wait for a qualifying transition to begin
+ or(
+ *toFlowArray(statesForHiddenKeyguard) { state ->
+ keyguardTransitionInteractor
+ .transitionStepsToState(state)
+ .map { it.value > 0f && it.transitionState == RUNNING }
+ .onStart { emit(false) }
+ }
+ )
+ .first { it }
emit(true)
- // Now await the signal that SHADE state has been reached or the GONE transition
- // was reversed. Until SHADE state has been replaced and merged with GONE, it is
- // the only source of when it is considered safe to reset alpha to 1f for HUNs.
+ // Now await the signal that SHADE state has been reached or the transition was
+ // reversed. Until SHADE state has been replaced it is the only source of when
+ // it is considered safe to reset alpha to 1f for HUNs.
combine(
keyguardInteractor.statusBarState,
- // Emit -1f on start to make sure the flow runs
- keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(-1f) }
- ) { statusBarState, goneValue ->
- statusBarState == SHADE || goneValue == 0f
+ and(
+ *toFlowArray(statesForHiddenKeyguard) { state ->
+ keyguardTransitionInteractor.transitionValue(state).map {
+ it == 0f
+ }
+ }
+ )
+ ) { statusBarState, stateIsReversed ->
+ statusBarState == SHADE || stateIsReversed
}
.first { it }
}
}
- .dumpWhileCollecting("goneTransitionInProgress")
+ .dumpWhileCollecting("isTransitioningToHiddenKeyguard")
fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
val alphaTransitions =
merge(
- alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
+ alternateBouncerToGoneTransitionViewModel.notificationAlpha(viewState),
aodToLockscreenTransitionViewModel.notificationAlpha,
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
@@ -425,7 +455,7 @@
goneToDreamingTransitionViewModel.lockscreenAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
- lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
+ lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
@@ -440,7 +470,7 @@
// shade expansion or swipe to dismiss
combineTransform(
isOnLockscreenWithoutShade,
- isGoneTransitionRunning,
+ isTransitioningToHiddenKeyguard,
shadeCollapseFadeIn,
alphaForShadeAndQsExpansion,
keyguardInteractor.dismissAlpha.dumpWhileCollecting(
@@ -448,7 +478,7 @@
),
) {
isOnLockscreenWithoutShade,
- isGoneTransitionRunning,
+ isTransitioningToHiddenKeyguard,
shadeCollapseFadeIn,
alphaForShadeAndQsExpansion,
dismissAlpha ->
@@ -456,7 +486,7 @@
if (!shadeCollapseFadeIn && dismissAlpha != null) {
emit(dismissAlpha)
}
- } else if (!isGoneTransitionRunning) {
+ } else if (!isTransitioningToHiddenKeyguard) {
emit(alphaForShadeAndQsExpansion)
}
},
@@ -559,8 +589,13 @@
combine(
isOnLockscreen,
keyguardInteractor.statusBarState,
- ) { isOnLockscreen, statusBarState ->
- statusBarState == SHADE_LOCKED || !isOnLockscreen
+ merge(
+ primaryBouncerToGoneTransitionViewModel.showAllNotifications,
+ alternateBouncerToGoneTransitionViewModel.showAllNotifications,
+ )
+ .onStart { emit(false) }
+ ) { isOnLockscreen, statusBarState, showAllNotifications ->
+ statusBarState == SHADE_LOCKED || !isOnLockscreen || showAllNotifications
}
return combineTransform(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index ad3012e5..e93c0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -52,6 +52,7 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
@@ -279,7 +280,9 @@
}
}
- mShadeHeaderController.disable(state1, state2, animate);
+ if (!SceneContainerFlag.isEnabled()) {
+ mShadeHeaderController.disable(state1, state2, animate);
+ }
}
/**
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 f76de04c..2798dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,6 +164,7 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
@@ -836,7 +837,6 @@
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
mLightRevealScrim = lightRevealScrim;
- // Based on teamfood flag, turn predictive back dispatch on at runtime.
if (predictiveBackSysui()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
@@ -1257,11 +1257,13 @@
mScreenOffAnimationController.initialize(this, mShadeSurface, mLightRevealScrim);
updateLightRevealScrimVisibility();
- mShadeSurface.initDependencies(
- this,
- mGestureRec,
- mShadeController::makeExpandedInvisible,
- mHeadsUpManager);
+ if (!SceneContainerFlag.isEnabled()) {
+ mShadeSurface.initDependencies(
+ this,
+ mGestureRec,
+ mShadeController::makeExpandedInvisible,
+ mHeadsUpManager);
+ }
// Set up the quick settings tile panel
final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
@@ -2629,11 +2631,10 @@
boolean goingToSleepWithoutAnimation = isGoingToSleep()
&& !mDozeParameters.shouldControlScreenOff();
boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
- || goingToSleepWithoutAnimation
- || mDeviceProvisionedController.isFrpActive();
+ || goingToSleepWithoutAnimation;
mShadeLogger.logUpdateNotificationPanelTouchState(disabled, isGoingToSleep(),
!mDozeParameters.shouldControlScreenOff(), !mDeviceInteractive,
- !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
+ !mDozeServiceHost.isPulsing());
mShadeSurface.setTouchAndAnimationDisabled(disabled);
if (!NotificationIconContainerRefactor.isEnabled()) {
@@ -3060,6 +3061,9 @@
public void onConfigChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
+ if (predictiveBackSysui()) {
+ mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
if (DEBUG) {
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 82b10bc..eadb7f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,12 +23,12 @@
import android.widget.FrameLayout
import androidx.annotation.StringRes
import com.android.keyguard.LockIconViewController
-import com.android.systemui.res.R
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
/**
@@ -155,13 +155,13 @@
// make top of ambient indication view the bottom of the lock icon
it.layout(
ambientLeft,
- lockIconViewController?.bottom?.toInt() ?: 0,
+ lockIconViewController?.getBottom()?.toInt() ?: 0,
right - ambientLeft,
ambientTop + it.measuredHeight
)
} else {
// make bottom of ambient indication view the top of the lock icon
- val lockLocationTop = lockIconViewController?.top ?: 0
+ val lockLocationTop = lockIconViewController?.getTop() ?: 0
it.layout(
ambientLeft,
lockLocationTop.toInt() - it.measuredHeight,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index febe5a2..afd2415 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -579,7 +579,7 @@
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null) {
viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_OVERLAY, mOnBackInvokedCallback);
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
mIsBackCallbackRegistered = true;
} else {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index c193783..79ea59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -247,7 +247,7 @@
if (nowExpanded) {
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
- } else if (clickedEntry.isSensitive()
+ } else if (clickedEntry.isSensitive().getValue()
&& mDynamicPrivacyController.isInLockedDownShade()) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 617e107..c52132f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -46,7 +46,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameView;
import com.android.systemui.statusbar.OperatorNameViewController;
@@ -79,6 +79,8 @@
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -89,8 +91,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
import kotlinx.coroutines.DisposableHandle;
/**
@@ -115,7 +115,7 @@
private PhoneStatusBarView mStatusBar;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
- private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private MultiSourceMinAlphaController mEndSideAlphaController;
private LinearLayout mEndSideContent;
private View mClockView;
@@ -227,7 +227,7 @@
CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
KeyguardStateController keyguardStateController,
- ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
StatusBarStateController statusBarStateController,
NotificationIconContainerStatusBarViewBinder nicViewBinder,
CommandQueue commandQueue,
@@ -252,7 +252,7 @@
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mDarkIconManagerFactory = darkIconManagerFactory;
mKeyguardStateController = keyguardStateController;
- mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mStatusBarStateController = statusBarStateController;
mNicViewBinder = nicViewBinder;
mCommandQueue = commandQueue;
@@ -603,7 +603,7 @@
private boolean shouldHideStatusBar() {
if (!mShadeExpansionStateManager.isClosed()
- && mShadeViewController.shouldHideStatusBarIconsWhenExpanded()) {
+ && mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
index c6c8823..684e38e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
/**
@@ -40,7 +41,7 @@
private val mobileConnectionsRepository: MobileConnectionsRepository,
) {
/** True if the device is currently in airplane mode. */
- val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
+ val isAirplaneMode: StateFlow<Boolean> = airplaneModeRepository.isAirplaneMode
/** True if we're configured to force-hide the airplane mode icon and false otherwise. */
val isForceHidden: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 0d09fc1..e432158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -50,9 +50,6 @@
*/
boolean isCurrentUserSetup();
- /** Returns true when Factory Reset Protection is locking the device. */
- boolean isFrpActive();
-
/**
* Interface to provide calls when the values tracked change
*/
@@ -73,10 +70,5 @@
* Call when some user changes from not provisioned to provisioned
*/
default void onUserSetupChanged() { }
-
- /**
- * Called when the state of FRP changes.
- */
- default void onFrpActiveChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 8b20283..8b63dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -36,7 +36,6 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.wrapper.BuildInfo
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
@@ -48,7 +47,6 @@
private val globalSettings: GlobalSettings,
private val userTracker: UserTracker,
private val dumpManager: DumpManager,
- private val buildInfo: BuildInfo,
@Background private val backgroundHandler: Handler,
@Main private val mainExecutor: Executor
) : DeviceProvisionedController,
@@ -62,11 +60,9 @@
}
private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
- private val frpActiveUri = globalSettings.getUriFor(Settings.Global.SECURE_FRP_MODE)
private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
private val deviceProvisioned = AtomicBoolean(false)
- private val frpActive = AtomicBoolean(false)
@GuardedBy("lock")
private val userSetupComplete = SparseBooleanArray()
@GuardedBy("lock")
@@ -93,15 +89,11 @@
userId: Int
) {
val updateDeviceProvisioned = deviceProvisionedUri in uris
- val updateFrp = frpActiveUri in uris
val updateUser = if (userSetupUri in uris) userId else NO_USERS
- updateValues(updateDeviceProvisioned, updateFrp, updateUser)
+ updateValues(updateDeviceProvisioned, updateUser)
if (updateDeviceProvisioned) {
onDeviceProvisionedChanged()
}
- if (updateFrp) {
- onFrpActiveChanged()
- }
if (updateUser != NO_USERS) {
onUserSetupChanged()
}
@@ -111,7 +103,7 @@
private val userChangedCallback = object : UserTracker.Callback {
@WorkerThread
override fun onUserChanged(newUser: Int, userContext: Context) {
- updateValues(updateDeviceProvisioned = false, updateFrp = false, updateUser = newUser)
+ updateValues(updateDeviceProvisioned = false, updateUser = newUser)
onUserSwitched()
}
@@ -133,23 +125,18 @@
updateValues()
userTracker.addCallback(userChangedCallback, backgroundExecutor)
globalSettings.registerContentObserver(deviceProvisionedUri, observer)
- globalSettings.registerContentObserver(frpActiveUri, observer)
secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
}
@WorkerThread
private fun updateValues(
- updateDeviceProvisioned: Boolean = true,
- updateFrp: Boolean = true,
- updateUser: Int = ALL_USERS
+ updateDeviceProvisioned: Boolean = true,
+ updateUser: Int = ALL_USERS
) {
if (updateDeviceProvisioned) {
deviceProvisioned
.set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
}
- if (updateFrp) {
- frpActive.set(globalSettings.getInt(Settings.Global.SECURE_FRP_MODE, 0) != 0)
- }
synchronized(lock) {
if (updateUser == ALL_USERS) {
val n = userSetupComplete.size()
@@ -188,10 +175,6 @@
return deviceProvisioned.get()
}
- override fun isFrpActive(): Boolean {
- return frpActive.get() && !buildInfo.isDebuggable
- }
-
override fun isUserSetup(user: Int): Boolean {
val index = synchronized(lock) {
userSetupComplete.indexOfKey(user)
@@ -220,12 +203,6 @@
)
}
- override fun onFrpActiveChanged() {
- dispatchChange(
- DeviceProvisionedController.DeviceProvisionedListener::onFrpActiveChanged
- )
- }
-
override fun onUserSetupChanged() {
dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSetupChanged)
}
@@ -247,7 +224,6 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("Device provisioned: ${deviceProvisioned.get()}")
- pw.println("Factory Reset Protection active: ${frpActive.get()}")
synchronized(lock) {
pw.println("User setup complete: $userSetupComplete")
pw.println("Listeners: $listeners")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9633cb0..1fc7bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -45,7 +45,6 @@
import android.view.MotionEvent;
import android.view.OnReceiveContentListener;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
@@ -85,9 +84,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.wm.shell.animation.Interpolators;
import java.util.ArrayList;
import java.util.Collection;
@@ -132,8 +129,6 @@
private boolean mColorized;
private int mLastBackgroundColor;
private boolean mResetting;
- @Nullable
- private RevealParams mRevealParams;
private Rect mContentBackgroundBounds;
private boolean mIsAnimatingAppearance = false;
@@ -453,6 +448,7 @@
setTopMargin(0);
if (grandParent != null) grandParent.setClipChildren(true);
setVisibility(GONE);
+ setAlpha(1f);
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(false);
}
@@ -464,20 +460,6 @@
if (actionsContainer != null) actionsContainer.setAlpha(0f);
animator.start();
- } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
- android.animation.Animator reveal = mRevealParams.createCircularHideAnimator(this);
- reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
- reveal.addListener(new android.animation.AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(android.animation.Animator animation) {
- setVisibility(GONE);
- if (mWrapper != null) {
- mWrapper.setRemoteInputVisible(false);
- }
- }
- });
- reveal.start();
} else {
setVisibility(GONE);
if (doAfterDefocus != null) doAfterDefocus.run();
@@ -719,10 +701,6 @@
mRemoved = true;
}
- public void setRevealParameters(@Nullable RevealParams revealParams) {
- mRevealParams = revealParams;
- }
-
@Override
public void dispatchStartTemporaryDetach() {
super.dispatchStartTemporaryDetach();
@@ -1177,24 +1155,4 @@
}
}
-
- public static class RevealParams {
- final int centerX;
- final int centerY;
- final int radius;
-
- public RevealParams(int centerX, int centerY, int radius) {
- this.centerX = centerX;
- this.centerY = centerY;
- this.radius = radius;
- }
-
- android.animation.Animator createCircularHideAnimator(View view) {
- return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
- }
-
- android.animation.Animator createCircularRevealAnimator(View view) {
- return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index bfee9ad..f619369 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -30,14 +30,13 @@
import android.util.Log
import android.view.View
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo
import com.android.systemui.statusbar.policy.RemoteInputView.NotificationRemoteInputEvent
-import com.android.systemui.statusbar.policy.RemoteInputView.RevealParams
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewScope
import javax.inject.Inject
@@ -61,8 +60,6 @@
/** Other [RemoteInput]s from the notification associated with this Controller. */
var remoteInputs: Array<RemoteInput>?
- var revealParams: RevealParams?
-
/**
* Sets the smart reply that should be inserted in the remote input, or `null` if the user is
* not editing a smart reply.
@@ -91,7 +88,6 @@
other.close()
remoteInput = other.remoteInput
remoteInputs = other.remoteInputs
- revealParams = other.revealParams
pendingIntent = other.pendingIntent
focus()
}
@@ -142,14 +138,6 @@
override var pendingIntent: PendingIntent? = null
override var remoteInputs: Array<RemoteInput>? = null
- override var revealParams: RevealParams? = null
- set(value) {
- field = value
- if (isBound) {
- view.setRevealParameters(value)
- }
- }
-
override val isActive: Boolean get() = view.isActive
override fun bind() {
@@ -161,7 +149,6 @@
view.setHintText(it.label)
view.setSupportedMimeTypes(it.allowedDataTypes)
}
- view.setRevealParameters(revealParams)
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 068e0a6..860068c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -94,7 +94,8 @@
int packageUid;
try {
- packageUid = mPackageManager.getPackageUid(info.getPackageName(), 0);
+ packageUid = mPackageManager.getPackageUidAsUser(info.getPackageName(),
+ info.getUserHandle().getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
Log.w(LOG_TAG, "Package " + info.getPackageName() + " not found");
packageUid = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
index 1160d65..4838554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepository.kt
@@ -31,9 +31,6 @@
* @see android.provider.Settings.Global.DEVICE_PROVISIONED
*/
val isDeviceProvisioned: Flow<Boolean>
-
- /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
- val isFactoryResetProtectionActive: Flow<Boolean>
}
@Module
@@ -58,16 +55,4 @@
trySend(deviceProvisionedController.isDeviceProvisioned)
awaitClose { deviceProvisionedController.removeCallback(listener) }
}
-
- override val isFactoryResetProtectionActive: Flow<Boolean> = conflatedCallbackFlow {
- val listener =
- object : DeviceProvisionedController.DeviceProvisionedListener {
- override fun onFrpActiveChanged() {
- trySend(deviceProvisionedController.isFrpActive)
- }
- }
- deviceProvisionedController.addCallback(listener)
- trySend(deviceProvisionedController.isFrpActive)
- awaitClose { deviceProvisionedController.removeCallback(listener) }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
index 32cf86d..66ed092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/DeviceProvisioningInteractor.kt
@@ -34,7 +34,4 @@
* @see android.provider.Settings.Global.DEVICE_PROVISIONED
*/
val isDeviceProvisioned: Flow<Boolean> = repository.isDeviceProvisioned
-
- /** Whether Factory Reset Protection (FRP) is currently active, locking the device. */
- val isFactoryResetProtectionActive: Flow<Boolean> = repository.isFactoryResetProtectionActive
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
index dc004f3..d66fe891 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -24,50 +24,53 @@
/** Utilities related to user management actions. */
object UserActionsUtil {
- /** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
+ /**
+ * Returns `true` if it's possible for the given user to add a guest user to the device; `false`
+ * otherwise.
+ */
fun canCreateGuest(
manager: UserManager,
repository: UserRepository,
isUserSwitcherEnabled: Boolean,
- isAddUsersFromLockScreenEnabled: Boolean,
+ canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
): Boolean {
- if (!isUserSwitcherEnabled) {
- return false
- }
-
- return currentUserCanCreateUsers(manager, repository) ||
- anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
+ return canAddMoreUsers(
+ manager,
+ repository,
+ isUserSwitcherEnabled,
+ canAddUsersWhenLockedOrDeviceUnlocked,
+ UserManager.USER_TYPE_FULL_GUEST
+ )
}
- /** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
+ /**
+ * Returns `true` if it's possible for the given user to add a user to the device; `false`
+ * otherwise.
+ */
fun canCreateUser(
manager: UserManager,
repository: UserRepository,
isUserSwitcherEnabled: Boolean,
- isAddUsersFromLockScreenEnabled: Boolean,
+ canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
): Boolean {
- if (!isUserSwitcherEnabled) {
- return false
- }
-
- if (
- !currentUserCanCreateUsers(manager, repository) &&
- !anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
- ) {
- return false
- }
-
- return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
+ return canAddMoreUsers(
+ manager,
+ repository,
+ isUserSwitcherEnabled,
+ canAddUsersWhenLockedOrDeviceUnlocked,
+ UserManager.USER_TYPE_FULL_SECONDARY
+ )
}
/**
- * Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
+ * Returns `true` if it's possible to add a supervised user to the device given the current
+ * user; false` otherwise.
*/
fun canCreateSupervisedUser(
manager: UserManager,
repository: UserRepository,
isUserSwitcherEnabled: Boolean,
- isAddUsersFromLockScreenEnabled: Boolean,
+ canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
supervisedUserPackageName: String?
): Boolean {
if (supervisedUserPackageName.isNullOrEmpty()) {
@@ -78,17 +81,30 @@
manager,
repository,
isUserSwitcherEnabled,
- isAddUsersFromLockScreenEnabled
+ canAddUsersWhenLockedOrDeviceUnlocked
)
}
- fun canManageUsers(
+ fun canManageUsers(repository: UserRepository, isUserSwitcherEnabled: Boolean): Boolean {
+ return isUserSwitcherEnabled && repository.getSelectedUserInfo().isAdmin
+ }
+
+ /**
+ * Returns `true` if it's possible to add a user to the device for the given user type; false
+ * otherwise.
+ */
+ private fun canAddMoreUsers(
+ manager: UserManager,
repository: UserRepository,
isUserSwitcherEnabled: Boolean,
- isAddUsersFromLockScreenEnabled: Boolean,
+ canAddUsersWhenLockedOrDeviceUnlocked: Boolean,
+ userType: String
): Boolean {
- return isUserSwitcherEnabled &&
- (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled)
+ if (!isUserSwitcherEnabled || !canAddUsersWhenLockedOrDeviceUnlocked) {
+ return false
+ }
+
+ return currentUserCanCreateUsers(manager, repository) && manager.canAddMoreUsers(userType)
}
/**
@@ -96,28 +112,15 @@
*/
private fun currentUserCanCreateUsers(
manager: UserManager,
- repository: UserRepository,
+ repository: UserRepository
): Boolean {
val currentUser = repository.getSelectedUserInfo()
if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
return false
}
-
- return systemCanCreateUsers(manager)
- }
-
- /** Returns `true` if the system can add users to the device; `false` otherwise. */
- private fun systemCanCreateUsers(
- manager: UserManager,
- ): Boolean {
- return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
- }
-
- /** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
- private fun anyoneCanCreateUsers(
- manager: UserManager,
- isAddUsersFromLockScreenEnabled: Boolean,
- ): Boolean {
- return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
+ return !manager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(currentUser.id)
+ ) && !manager.hasUserRestrictionForUser(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index a122311..382bc03 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -170,7 +170,8 @@
keyguardInteractor.isKeyguardShowing,
) { _, userInfos, settings, isDeviceLocked ->
buildList {
- if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+ val canAccessUserSwitcher = !isDeviceLocked || settings.isAddUsersFromLockscreen
+ if (canAccessUserSwitcher) {
// The device is locked and our setting to allow actions that add users
// from the lock-screen is not enabled. We can finish building the list
// here.
@@ -194,7 +195,10 @@
when (it) {
UserActionModel.ENTER_GUEST_MODE -> {
val hasGuestUser = userInfos.any { it.isGuest }
- if (!hasGuestUser && canCreateGuestUser(settings)) {
+ if (
+ !hasGuestUser &&
+ canCreateGuestUser(settings, canAccessUserSwitcher)
+ ) {
add(UserActionModel.ENTER_GUEST_MODE)
}
}
@@ -204,7 +208,7 @@
manager,
repository,
settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
+ canAccessUserSwitcher
)
if (canCreateUsers) {
@@ -217,7 +221,7 @@
manager,
repository,
settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
+ canAccessUserSwitcher,
supervisedUserPackageName,
)
) {
@@ -229,11 +233,7 @@
}
}
if (
- UserActionsUtil.canManageUsers(
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- )
+ UserActionsUtil.canManageUsers(repository, settings.isUserSwitcherEnabled)
) {
add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
@@ -820,13 +820,16 @@
)
}
- private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+ private fun canCreateGuestUser(
+ settings: UserSwitcherSettingsModel,
+ canAccessUserSwitcher: Boolean
+ ): Boolean {
return guestUserInteractor.isGuestUserAutoCreated ||
UserActionsUtil.canCreateGuest(
manager,
repository,
settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
+ canAccessUserSwitcher,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 0207d6e..04d7b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -36,10 +36,15 @@
}
fun onSettingsClicked() {
- activityStarter.startActivity(
- Intent(Settings.ACTION_SOUND_SETTINGS)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
- true,
- ) { volumePanelViewModel.dismissPanel() }
+ activityStarter.startActivityDismissingKeyguard(
+ /* intent = */ Intent(Settings.ACTION_SOUND_SETTINGS),
+ /* onlyProvisioned = */ false,
+ /* dismissShade = */ true,
+ /* disallowEnterPictureInPictureWhileLaunching = */ false,
+ /* callback = */ { volumePanelViewModel.dismissPanel() },
+ /* flags = */ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
+ /* animationController = */ null,
+ /* userHandle = */ null,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 3242c28..57b5d57 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -98,7 +98,7 @@
}
}
- private fun AudioStreamModel.toState(
+ private suspend fun AudioStreamModel.toState(
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
@@ -116,7 +116,7 @@
isEnabled = isEnabled,
a11yStep = volumeRange.step,
audioStreamModel = this,
- isMutable = audioVolumeInteractor.isMutable(audioStream),
+ isMutable = audioVolumeInteractor.isAffectedByMute(audioStream),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 1d9b90a..ff18418 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -91,17 +91,22 @@
@Background Executor bgExecutor,
SecureSettings secureSettings,
QuickAccessWalletClient quickAccessWalletClient,
- SystemClock clock) {
+ SystemClock clock,
+ RoleManager roleManager) {
mContext = context;
mExecutor = executor;
mBgExecutor = bgExecutor;
mSecureSettings = secureSettings;
- mRoleManager = mContext.getSystemService(RoleManager.class);
+ mRoleManager = roleManager;
mQuickAccessWalletClient = quickAccessWalletClient;
mClock = clock;
mQawClientCreatedTimeMillis = mClock.elapsedRealtime();
}
+ public boolean isWalletRoleAvailable() {
+ return mRoleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
+ }
+
/**
* Returns true if the Quick Access Wallet service & feature is available.
*/
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
index 1c77351..f924ab4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
@@ -68,7 +68,7 @@
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-public class LockIconViewControllerBaseTest extends SysuiTestCase {
+public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase {
protected static final String UNLOCKED_LABEL = "unlocked";
protected static final String LOCKED_LABEL = "locked";
protected static final int PADDING = 10;
@@ -98,7 +98,7 @@
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
- protected LockIconViewController mUnderTest;
+ protected LegacyLockIconViewController mUnderTest;
// Capture listeners so that they can be used to send events
@Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
@@ -153,7 +153,7 @@
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false);
- mUnderTest = new LockIconViewController(
+ mUnderTest = new LegacyLockIconViewController(
mStatusBarStateController,
mKeyguardUpdateMonitor,
mKeyguardViewController,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
index b0887ef..8689842 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
@@ -51,7 +51,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControllerBaseTest {
@Override
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt
index 1213518..25a87b8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt
@@ -39,7 +39,7 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
-class LockIconViewControllerWithCoroutinesTest : LockIconViewControllerBaseTest() {
+class LegacyLockIconViewControllerWithCoroutinesTest : LegacyLockIconViewControllerBaseTest() {
/** After migration, replaces LockIconViewControllerTest version */
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
new file mode 100644
index 0000000..12f334b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.window.InputTransferToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Supplier;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class FullscreenMagnificationControllerTest extends SysuiTestCase {
+
+ private FullscreenMagnificationController mFullscreenMagnificationController;
+ private SurfaceControlViewHost mSurfaceControlViewHost;
+
+ @Before
+ public void setUp() {
+ getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
+ new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification"));
+
+ Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+
+ mFullscreenMagnificationController = new FullscreenMagnificationController(
+ mContext,
+ mContext.getSystemService(AccessibilityManager.class),
+ mContext.getSystemService(WindowManager.class),
+ scvhSupplier);
+ }
+
+ @After
+ public void tearDown() {
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
+ }
+
+ @Test
+ public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true)
+ );
+
+ // Wait for Rects updated.
+ waitForIdleSync();
+ assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
+ }
+
+ @Test
+ public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
+ getInstrumentation().runOnMainSync(
+ () -> {
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true);
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false);
+ }
+ );
+
+ assertThat(mSurfaceControlViewHost.getView()).isNull();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index bd49927..41d5d5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -71,6 +71,8 @@
@Mock
private WindowMagnificationController mWindowMagnificationController;
@Mock
+ private FullscreenMagnificationController mFullscreenMagnificationController;
+ @Mock
private MagnificationSettingsController mMagnificationSettingsController;
@Mock
private ModeSwitchesController mModeSwitchesController;
@@ -105,6 +107,9 @@
mMagnification.mWindowMagnificationControllerSupplier =
new FakeWindowMagnificationControllerSupplier(
mContext.getSystemService(DisplayManager.class));
+ mMagnification.mFullscreenMagnificationControllerSupplier =
+ new FakeFullscreenMagnificationControllerSupplier(
+ mContext.getSystemService(DisplayManager.class));
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class));
@@ -124,6 +129,15 @@
}
@Test
+ public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
+ mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
+ waitForIdleSync();
+
+ verify(mFullscreenMagnificationController)
+ .onFullscreenMagnificationActivationChanged(eq(true));
+ }
+
+ @Test
public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
mAnimationCallback);
@@ -215,6 +229,20 @@
}
}
+
+ private class FakeFullscreenMagnificationControllerSupplier extends
+ DisplayIdIndexSupplier<FullscreenMagnificationController> {
+
+ FakeFullscreenMagnificationControllerSupplier(DisplayManager displayManager) {
+ super(displayManager);
+ }
+
+ @Override
+ protected FullscreenMagnificationController createInstance(Display display) {
+ return mFullscreenMagnificationController;
+ }
+ }
+
private class FakeSettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de696f4..71f6081 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -165,7 +165,7 @@
mMenuView = spy(
new MenuView(mSpyContext, mMenuViewModel, menuViewAppearance, mSecureSettings));
// Ensure tests don't actually update metrics.
- doNothing().when(mMenuView).incrementTexMetric(any(), anyInt());
+ doNothing().when(mMenuView).incrementTexMetric(any());
mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
@@ -414,33 +414,25 @@
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onDismissAction_incrementsTexMetricDismiss() {
- int uid1 = 1234, uid2 = 5678;
mMenuViewModel.onTargetFeaturesChanged(
- List.of(new TestAccessibilityTarget(mSpyContext, uid1),
- new TestAccessibilityTarget(mSpyContext, uid2)));
+ List.of(new TestAccessibilityTarget(mSpyContext, 1234),
+ new TestAccessibilityTarget(mSpyContext, 5678)));
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
- ArgumentCaptor<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mMenuView, times(2)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_DISMISS),
- uidCaptor.capture());
- assertThat(uidCaptor.getAllValues()).containsExactly(uid1, uid2);
+ verify(mMenuView, times(1)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_DISMISS));
}
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onEditAction_incrementsTexMetricEdit() {
- int uid1 = 1234, uid2 = 5678;
mMenuViewModel.onTargetFeaturesChanged(
- List.of(new TestAccessibilityTarget(mSpyContext, uid1),
- new TestAccessibilityTarget(mSpyContext, uid2)));
+ List.of(new TestAccessibilityTarget(mSpyContext, 1234),
+ new TestAccessibilityTarget(mSpyContext, 5678)));
mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
- ArgumentCaptor<Integer> uidCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mMenuView, times(2)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_EDIT),
- uidCaptor.capture());
- assertThat(uidCaptor.getAllValues()).containsExactly(uid1, uid2);
+ verify(mMenuView, times(1)).incrementTexMetric(eq(MenuViewLayer.TEX_METRIC_EDIT));
}
/** Simplified AccessibilityTarget for testing MenuViewLayer. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
new file mode 100644
index 0000000..38e3171
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+
+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;
+
+/** Tests for {@link HearingDevicesDialogDelegate}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private SystemUIDialog.Factory mSystemUIDialogFactory;
+ @Mock
+ private SystemUIDialogManager mSystemUIDialogManager;
+ @Mock
+ private SysUiState mSysUiState;
+ @Mock
+ private DialogTransitionAnimator mDialogTransitionAnimator;
+ private SystemUIDialog mDialog;
+ private HearingDevicesDialogDelegate mDialogDelegate;
+
+ @Before
+ public void setUp() {
+ mDialogDelegate = new HearingDevicesDialogDelegate(mSystemUIDialogFactory);
+ mDialog = new SystemUIDialog(
+ mContext,
+ 0,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ mSystemUIDialogManager,
+ mSysUiState,
+ getFakeBroadcastDispatcher(),
+ mDialogTransitionAnimator,
+ mDialogDelegate
+ );
+
+ when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
+ .thenReturn(mDialog);
+ }
+
+ @Test
+ public void createDialog_dialogShown() {
+ assertThat(mDialogDelegate.createDialog()).isEqualTo(mDialog);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
index 3bdbf97..190babd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
@@ -23,11 +23,11 @@
@Test
fun sysUi_floatingSystemSurfaces_animationValues() {
- val maxX = 14.0f
- val maxY = 4.0f
- val minScale = 0.8f
+ val maxX = 19.0f
+ val maxY = 14.0f
+ val minScale = 0.9f
- val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics)
+ val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi { displayMetrics }
assertBackTransformation(
backAnimationSpec = backAnimationSpec,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 2b95973..314abda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -4,7 +4,10 @@
import android.window.BackEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -27,7 +30,7 @@
private val onBackAnimationCallback =
onBackAnimationCallbackFrom(
- backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics),
+ backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi { displayMetrics },
displayMetrics = displayMetrics,
onBackProgressed = onBackProgress,
onBackStarted = onBackStart,
@@ -48,7 +51,14 @@
onBackAnimationCallback.onBackProgressed(backEvent)
- verify(onBackProgress).invoke(BackTransformation(0f, 0f, 1f))
+ val argumentCaptor = argumentCaptor<BackTransformation>()
+ verify(onBackProgress).invoke(capture(argumentCaptor))
+
+ val actual = argumentCaptor.value
+ val tolerance = 0.0001f
+ assertThat(actual.translateX).isWithin(tolerance).of(0f)
+ assertThat(actual.translateY).isWithin(tolerance).of(0f)
+ assertThat(actual.scale).isWithin(tolerance).of(1f)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 51828c9..6ebda4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,6 +14,7 @@
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewRootImpl
+import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.Flags
@@ -52,6 +53,8 @@
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
+ private lateinit var windowManager: WindowManager
+ @Mock
private lateinit var keyguardViewMediator: KeyguardViewMediator
@Mock
private lateinit var keyguardStateController: KeyguardStateController
@@ -99,7 +102,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
+ windowManager, context.resources,
+ keyguardStateController, { keyguardViewMediator }, keyguardViewController,
featureFlags, { biometricUnlockController }, statusBarStateController,
notificationShadeWindowController, powerManager, wallpaperManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 272b488..11ec417f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -306,28 +306,6 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void testRaceCondition_doNotRegisterCentralSurfacesImmediately() {
- create(false);
-
- // GIVEN central surfaces is not registered with KeyguardViewMediator, but a call to enable
- // keyguard comes in
- mViewMediator.onSystemReady();
- mViewMediator.setKeyguardEnabled(true);
- TestableLooper.get(this).processAllMessages();
-
- // If this step has been reached, then system ui has not crashed. Now register
- // CentralSurfaces
- assertFalse(mViewMediator.isShowingAndNotOccluded());
- register();
- TestableLooper.get(this).moveTimeForward(100);
- TestableLooper.get(this).processAllMessages();
-
- // THEN keyguard is shown
- assertTrue(mViewMediator.isShowingAndNotOccluded());
- }
-
- @Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
@@ -1206,11 +1184,6 @@
}
private void createAndStartViewMediator(boolean orderUnlockAndWake) {
- create(orderUnlockAndWake);
- register();
- }
-
- private void create(boolean orderUnlockAndWake) {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake);
@@ -1262,9 +1235,7 @@
mKeyguardInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
- }
- private void register() {
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 7bef01a..3926f92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -48,6 +48,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -73,13 +74,17 @@
}
private val testScope = kosmos.testScope
- private val underTest = kosmos.fromAodTransitionInteractor
+ private lateinit var underTest: FromAodTransitionInteractor
- private val powerInteractor = kosmos.powerInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private lateinit var powerInteractor: PowerInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Before
fun setup() {
+ powerInteractor = kosmos.powerInteractor
+ transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ underTest = kosmos.fromAodTransitionInteractor
+
underTest.start()
// Transition to AOD and set the power interactor asleep.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 258dbf3..cded2a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -77,13 +78,17 @@
}
private val testScope = kosmos.testScope
- private val underTest = kosmos.fromDozingTransitionInteractor
+ private lateinit var underTest: FromDozingTransitionInteractor
- private val powerInteractor = kosmos.powerInteractor
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private lateinit var powerInteractor: PowerInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Before
fun setup() {
+ powerInteractor = kosmos.powerInteractor
+ transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ underTest = kosmos.fromDozingTransitionInteractor
+
underTest.start()
// Transition to DOZING and set the power interactor asleep.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 58273d6..4f2b690 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -22,7 +22,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.keyguard.LockIconViewController
+import com.android.keyguard.LegacyLockIconViewController
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
@@ -58,7 +58,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
@Mock private lateinit var notificationPanelView: NotificationPanelView
private lateinit var featureFlags: FakeFeatureFlags
- @Mock private lateinit var lockIconViewController: LockIconViewController
+ @Mock private lateinit var lockIconViewController: LegacyLockIconViewController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel
private lateinit var underTest: DefaultDeviceEntrySection
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
new file mode 100644
index 0000000..8cc3a85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconTilesRepositoryImplTest : SysuiTestCase() {
+
+ private val underTest = IconTilesRepositoryImpl()
+
+ @Test
+ fun iconTilesSpecsIsValid() = runTest {
+ val tilesSpecs by collectLastValue(underTest.iconTilesSpecs)
+ assertThat(tilesSpecs).isEqualTo(ICON_ONLY_TILES_SPECS)
+ }
+
+ companion object {
+ private val ICON_ONLY_TILES_SPECS =
+ setOf(
+ TileSpec.create("airplane"),
+ TileSpec.create("battery"),
+ TileSpec.create("cameratoggle"),
+ TileSpec.create("cast"),
+ TileSpec.create("color_correction"),
+ TileSpec.create("inversion"),
+ TileSpec.create("saver"),
+ TileSpec.create("dnd"),
+ TileSpec.create("flashlight"),
+ TileSpec.create("location"),
+ TileSpec.create("mictoggle"),
+ TileSpec.create("nfc"),
+ TileSpec.create("night"),
+ TileSpec.create("rotation")
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
new file mode 100644
index 0000000..73aa54c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.logging.QSLogger;
+
+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;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link HearingDevicesTile}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class HearingDevicesTileTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private QSHost mHost;
+ @Mock
+ private QsEventLogger mUiEventLogger;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSLogger mQSLogger;
+ @Mock
+ HearingDevicesDialogManager mHearingDevicesDialogManager;
+
+ private TestableLooper mTestableLooper;
+ private HearingDevicesTile mTile;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+ when(mHost.getContext()).thenReturn(mContext);
+
+ mTile = new HearingDevicesTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mHearingDevicesDialogManager);
+
+ mTile.initialize();
+ mTestableLooper.processAllMessages();
+ }
+
+ @After
+ public void tearDown() {
+ mTile.destroy();
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+ public void isAvailable_flagEnabled_true() {
+ assertThat(mTile.isAvailable()).isTrue();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
+ public void isAvailable_flagDisabled_false() {
+ assertThat(mTile.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void longClick_expectedAction() {
+ mTile.longClick(null);
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<Intent> IntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(IntentCaptor.capture(),
+ anyInt(), any());
+ assertThat(IntentCaptor.getValue().getAction()).isEqualTo(
+ Settings.ACTION_HEARING_DEVICES_SETTINGS);
+ }
+
+ @Test
+ public void handleClick_dialogShown() {
+ View view = new View(mContext);
+ mTile.handleClick(view);
+ mTestableLooper.processAllMessages();
+
+ verify(mHearingDevicesDialogManager).showDialog(view);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 2d18f92..122d9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,7 +58,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -68,6 +67,7 @@
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
@@ -198,7 +198,8 @@
}
@Test
- public void testIsAvailable_qawFeatureAvailable() {
+ public void testIsAvailable_qawFeatureAvailableWalletUnavailable() {
+ when(mController.isWalletRoleAvailable()).thenReturn(false);
when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true);
when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
when(mSecureSettings.getStringForUser(NFC_PAYMENT_DEFAULT_COMPONENT,
@@ -208,6 +209,16 @@
}
@Test
+ public void testIsAvailable_nfcUnavailableWalletAvailable() {
+ when(mController.isWalletRoleAvailable()).thenReturn(true);
+ when(mHost.getUserId()).thenReturn(PRIMARY_USER_ID);
+ when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
+
+ assertTrue(mTile.isAvailable());
+ }
+
+ @Test
public void testHandleClick_startQuickAccessUiIntent_noCard() {
setUpWalletCard(/* hasCard= */ false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 6e48074..598a0f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -50,6 +49,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -59,7 +59,6 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
- //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var starter: ActivityStarter
@Mock private lateinit var controller: RecordingController
@Mock private lateinit var userContextProvider: UserContextProvider
@@ -76,13 +75,13 @@
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
val systemUIDialogFactory =
- SystemUIDialog.Factory(
- context,
- Dependency.get(SystemUIDialogManager::class.java),
- Dependency.get(SysUiState::class.java),
- Dependency.get(BroadcastDispatcher::class.java),
- Dependency.get(DialogTransitionAnimator::class.java),
- )
+ SystemUIDialog.Factory(
+ context,
+ Dependency.get(SystemUIDialogManager::class.java),
+ Dependency.get(SysUiState::class.java),
+ Dependency.get(BroadcastDispatcher::class.java),
+ Dependency.get(DialogTransitionAnimator::class.java),
+ )
val delegate =
ScreenRecordPermissionDialogDelegate(
@@ -189,6 +188,17 @@
verify(mediaProjectionMetricsLogger).notifyProjectionRequestCancelled(TEST_HOST_UID)
}
+ @Test
+ fun showDialog_singleAppSelected_clickOnStart_projectionRequestCancelledIsNotLoggedOnce() {
+ showDialog()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ clickOnStart()
+
+ verify(mediaProjectionMetricsLogger, never())
+ .notifyProjectionRequestCancelled(TEST_HOST_UID)
+ }
+
private fun showDialog() {
dialog.show()
}
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 7e41745..0847f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -30,8 +30,6 @@
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
@@ -44,35 +42,8 @@
private val component = ComponentName("android.test", "android.test.Component")
private val bounds = Rect(25, 25, 75, 75)
- private val scope = CoroutineScope(Dispatchers.Unconfined)
private val policy = FakeScreenshotPolicy()
- /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
- @Test
- fun testProcessAsync_ScreenshotData() {
- val request =
- ScreenshotData.fromRequest(
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_KEY_OTHER)
- .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build()
- )
- val processor = RequestProcessor(imageCapture, policy, scope)
-
- var result: ScreenshotData? = null
- var callbackCount = 0
- val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData ->
- result = processedRequest
- callbackCount++
- }
-
- // runs synchronously, using Unconfined Dispatcher
- processor.processAsync(request, callback)
-
- // Callback invoked once returning the same request (no changes)
- assertThat(callbackCount).isEqualTo(1)
- assertThat(result).isEqualTo(request)
- }
-
@Test
fun testFullScreenshot() = runBlocking {
// Indicate that the primary content belongs to a normal user
@@ -84,7 +55,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -109,7 +80,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -136,7 +107,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -146,7 +117,7 @@
@Test
fun testProvidedImageScreenshot() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
policy.setManagedProfile(USER_ID, false)
@@ -171,7 +142,7 @@
@Test
fun testProvidedImageScreenshot_managedProfile() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, scope)
+ val processor = RequestProcessor(imageCapture, policy)
// Indicate that the screenshot belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 0baee5d..c900463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -57,7 +57,7 @@
private val eventLogger = UiEventLoggerFake()
private val screenshotExecutor =
- TakeScreenshotExecutor(
+ TakeScreenshotExecutorImpl(
controllerFactory,
fakeDisplayRepository,
testScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index f3809aa..0776aa7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -20,134 +20,97 @@
import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
import android.app.admin.DevicePolicyResourcesManager
import android.content.ComponentName
+import android.net.Uri
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
-import android.view.Display
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.MULTI_DISPLAY_SCREENSHOT
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import java.util.function.Consumer
+import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.isNull
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doThrow
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
-@SmallTest
class TakeScreenshotServiceTest : SysuiTestCase() {
- private val application = mock<Application>()
- private val controller = mock<ScreenshotController>()
- private val controllerFactory = mock<ScreenshotController.Factory>()
- private val takeScreenshotExecutor = mock<TakeScreenshotExecutor>()
- private val userManager = mock<UserManager>()
- private val requestProcessor = mock<RequestProcessor>()
- private val devicePolicyManager = mock<DevicePolicyManager>()
- private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
- private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
+ private val userManager = mock<UserManager> { on { isUserUnlocked } doReturn (true) }
+
+ private val devicePolicyResourcesManager =
+ mock<DevicePolicyResourcesManager> {
+ on { getString(eq(SCREENSHOT_BLOCKED_BY_ADMIN), /* defaultStringLoader= */ any()) }
+ .doReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+ }
+
+ private val devicePolicyManager =
+ mock<DevicePolicyManager> {
+ on { resources } doReturn (devicePolicyResourcesManager)
+ on { getScreenCaptureDisabled(/* admin= */ isNull(), eq(UserHandle.USER_ALL)) }
+ .doReturn(false)
+ }
+
private val notificationsController = mock<ScreenshotNotificationsController>()
- private val callback = mock<RequestCallback>()
+ private val notificationsControllerFactory =
+ ScreenshotNotificationsController.Factory { notificationsController }
+ private val executor = FakeScreenshotExecutor()
+ private val callback = FakeRequestCallback()
private val eventLogger = UiEventLoggerFake()
- private val flags = FakeFeatureFlags()
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
- private lateinit var service: TakeScreenshotService
-
- @Before
- fun setUp() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, false)
- whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
- whenever(
- devicePolicyManager.getScreenCaptureDisabled(
- /* admin component (null: any admin) */ isNull(),
- eq(UserHandle.USER_ALL)
- )
- )
- .thenReturn(false)
- whenever(userManager.isUserUnlocked).thenReturn(true)
- whenever(controllerFactory.create(any(), any())).thenReturn(controller)
- whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
-
- // Stub request processor as a synchronous no-op for tests with the flag enabled
- doAnswer {
- val request: ScreenshotData = it.getArgument(0) as ScreenshotData
- val consumer: Consumer<ScreenshotData> = it.getArgument(1)
- consumer.accept(request)
- }
- .whenever(requestProcessor)
- .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
-
- service = createService()
- }
-
@Test
fun testServiceLifecycle() {
+ val service = createService()
service.onCreate()
service.onBind(null /* unused: Intent */)
+ assertThat(executor.windowsPresent).isTrue()
service.onUnbind(null /* unused: Intent */)
- verify(controller, times(1)).removeWindow()
+ assertThat(executor.windowsPresent).isFalse()
service.onDestroy()
- verify(controller, times(1)).onDestroy()
+ assertThat(executor.destroyed).isTrue()
}
@Test
fun takeScreenshotFullscreen() {
+ val service = createService()
+
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
.build()
service.handleRequest(request, { /* onSaved */}, callback)
+ assertWithMessage("request received by executor").that(executor.requestReceived).isNotNull()
- verify(controller, times(1))
- .handleScreenshot(
- eq(ScreenshotData.fromRequest(request, Display.DEFAULT_DISPLAY)),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any()
- )
-
- assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
- val logEvent = eventLogger.get(0)
-
- assertEquals(
- "Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId,
- SCREENSHOT_REQUESTED_KEY_OTHER.id
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- eventLogger.get(0).packageName
- )
+ assertWithMessage("request received by executor")
+ .that(ScreenshotData.fromRequest(executor.requestReceived!!))
+ .isEqualTo(ScreenshotData.fromRequest(request))
}
@Test
fun takeScreenshotFullscreen_userLocked() {
- whenever(userManager.isUserUnlocked).thenReturn(false)
+ val service = createService()
+ whenever(userManager.isUserUnlocked).doReturn(false)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
@@ -157,47 +120,41 @@
service.handleRequest(request, { /* onSaved */}, callback)
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+ assertWithMessage("callback errorReported").that(callback.errorReported).isTrue()
+
+ assertWithMessage("UiEvent count").that(eventLogger.numLogs()).isEqualTo(2)
+
val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
+ assertWithMessage("request UiEvent id")
+ .that(requestEvent.eventId)
+ .isEqualTo(SCREENSHOT_REQUESTED_KEY_OTHER.id)
+
+ assertWithMessage("topComponent package name")
+ .that(requestEvent.packageName)
+ .isEqualTo(topComponent.packageName)
+
val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
+ assertWithMessage("failure UiEvent id")
+ .that(failureEvent.eventId)
+ .isEqualTo(SCREENSHOT_CAPTURE_FAILED.id)
+
+ assertWithMessage("Supplied package name")
+ .that(failureEvent.packageName)
+ .isEqualTo(topComponent.packageName)
}
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
- .thenReturn(true)
+ val service = createService()
whenever(
- devicePolicyResourcesManager.getString(
- eq(SCREENSHOT_BLOCKED_BY_ADMIN),
- /* Supplier<String> */
- any(),
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin= */ isNull(),
+ eq(UserHandle.USER_ALL)
)
)
- .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+ .doReturn(true)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
@@ -205,11 +162,9 @@
.build()
service.handleRequest(request, { /* onSaved */}, callback)
+ assertThat(callback.errorReported).isTrue()
+ assertWithMessage("Expected two UiEvents").that(eventLogger.numLogs()).isEqualTo(2)
- // error shown: Toast.makeText(...).show(), untestable
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
val requestEvent = eventLogger.get(0)
assertEquals(
"Expected SCREENSHOT_REQUESTED_* UiEvent",
@@ -234,112 +189,70 @@
)
}
- @Test
- fun takeScreenshot_workProfile_nullBitmap() {
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- doThrow(IllegalStateException::class.java)
- .whenever(requestProcessor)
- .processAsync(any(ScreenshotData::class.java), any())
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verify(callback, times(1)).reportError()
- verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
- val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
- val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
- }
-
- @Test
- fun takeScreenshotFullScreen_multiDisplayFlagEnabled_takeScreenshotExecutor() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- service = createService()
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verifyZeroInteractions(controller)
- verify(takeScreenshotExecutor, times(1)).executeScreenshotsAsync(any(), any(), any())
-
- assertEquals("Expected one UiEvent", 0, eventLogger.numLogs())
- }
-
- @Test
- fun testServiceLifecycle_multiDisplayScreenshotFlagEnabled() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- service = createService()
-
- service.onCreate()
- service.onBind(null /* unused: Intent */)
-
- service.onUnbind(null /* unused: Intent */)
- verify(takeScreenshotExecutor, times(1)).removeWindows()
-
- service.onDestroy()
- verify(takeScreenshotExecutor, times(1)).onDestroy()
- }
-
- @Test
- fun constructor_MultiDisplayFlagOn_screenshotControllerNotCreated() {
- flags.set(MULTI_DISPLAY_SCREENSHOT, true)
- clearInvocations(controllerFactory)
-
- service = createService()
-
- verifyZeroInteractions(controllerFactory)
- }
-
private fun createService(): TakeScreenshotService {
val service =
TakeScreenshotService(
- controllerFactory,
userManager,
devicePolicyManager,
eventLogger,
notificationsControllerFactory,
mContext,
Runnable::run,
- flags,
- requestProcessor,
- { takeScreenshotExecutor },
+ executor
)
+
service.attach(
mContext,
/* thread = */ null,
/* className = */ null,
/* token = */ null,
- application,
+ mock<Application>(),
/* activityManager = */ null
)
return service
}
}
+
+internal class FakeRequestCallback : RequestCallback {
+ var errorReported = false
+ var finished = false
+ override fun reportError() {
+ errorReported = true
+ }
+
+ override fun onFinish() {
+ finished = true
+ }
+}
+
+internal class FakeScreenshotExecutor : TakeScreenshotExecutor {
+ var requestReceived: ScreenshotRequest? = null
+ var windowsPresent = true
+ var destroyed = false
+ override fun onCloseSystemDialogsReceived() {}
+ override suspend fun executeScreenshots(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: (Uri) -> Unit,
+ requestCallback: RequestCallback,
+ ) {
+ requestReceived = screenshotRequest
+ }
+
+ override fun removeWindows() {
+ windowsPresent = false
+ }
+
+ override fun onDestroy() {
+ destroyed = true
+ }
+
+ override fun executeScreenshotsAsync(
+ screenshotRequest: ScreenshotRequest,
+ onSaved: Consumer<Uri>,
+ requestCallback: RequestCallback,
+ ) {
+ runBlocking {
+ executeScreenshots(screenshotRequest, { onSaved.accept(it) }, requestCallback)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 07d9350..5ca6cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -41,6 +41,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.testKosmos
@@ -104,7 +105,8 @@
dialogFactory,
keyguardTransitionInteractor,
shadeInteractor,
- powerManager
+ powerManager,
+ kosmos.sceneDataSourceDelegator,
)
testableLooper = TestableLooper.get(this)
@@ -145,6 +147,7 @@
keyguardTransitionInteractor,
shadeInteractor,
powerManager,
+ kosmos.sceneDataSourceDelegator,
)
// First call succeeds.
@@ -268,7 +271,7 @@
}
private fun goToScene(scene: SceneKey) {
- communalRepository.setDesiredScene(scene)
+ communalRepository.changeScene(scene)
testableLooper.processAllMessages()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 56e61e4..dfe72cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -73,7 +73,7 @@
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LockIconViewController;
+import com.android.keyguard.LegacyLockIconViewController;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -278,7 +278,7 @@
@Mock protected AmbientState mAmbientState;
@Mock protected UserManager mUserManager;
@Mock protected UiEventLogger mUiEventLogger;
- @Mock protected LockIconViewController mLockIconViewController;
+ @Mock protected LegacyLockIconViewController mLockIconViewController;
@Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator;
@Mock protected KeyguardRootView mKeyguardRootView;
@Mock protected View mKeyguardRootViewChild;
@@ -604,6 +604,7 @@
new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
+ mKosmos.getTestScope().getBackgroundScope(),
mDumpManager,
mock(HeadsUpManager.class),
new StatusBarStateControllerImpl(
@@ -618,7 +619,8 @@
mDozeParameters,
mScreenOffAnimationController,
new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
- notifsKeyguardInteractor);
+ notifsKeyguardInteractor,
+ mKosmos.getCommunalInteractor());
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
@@ -711,6 +713,7 @@
mFragmentService,
mStatusBarService,
mContentResolver,
+ mShadeHeaderController,
mScreenOffAnimationController,
mLockscreenGestureLogger,
mShadeExpansionStateManager,
@@ -896,8 +899,8 @@
mConfigurationController.onConfigurationChanged(configuration);
}
- protected void onTouchEvent(MotionEvent ev) {
- mTouchHandler.onTouch(mView, ev);
+ protected boolean onTouchEvent(MotionEvent ev) {
+ return mTouchHandler.onTouch(mView, ev);
}
protected void setDozing(boolean dozing, boolean dozingAlwaysOn) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 6d5d5be..650c45b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -364,6 +364,24 @@
}
@Test
+ public void alternateBouncerVisible_onTouchEvent_notHandled() {
+ mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ // GIVEN alternate bouncer is visible
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // WHEN touch DOWN event received; THEN touch is NOT handled
+ assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */))).isFalse();
+
+ // WHEN touch MOVE event received; THEN touch is NOT handled
+ assertThat(onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+ 0 /* metaState */))).isFalse();
+
+ }
+
+ @Test
public void test_onTouchEvent_startTracking() {
// GIVEN device is NOT pulsing
mNotificationPanelViewController.setPulsing(false);
@@ -1043,7 +1061,7 @@
@Test
public void testPanelClosedWhenClosingQsInSplitShade() {
mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
- /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ /* expanded= */ true, /* tracking= */ false);
enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.setExpandedFraction(1f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index b699613..dfbb699 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -25,7 +25,7 @@
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.LockIconViewController
+import com.android.keyguard.LegacyLockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -68,6 +69,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -85,7 +87,6 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.Optional
import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -100,7 +101,8 @@
@Mock private lateinit var dozeServiceHost: DozeServiceHost
@Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var dockManager: DockManager
- @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@@ -113,7 +115,7 @@
@Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
- @Mock private lateinit var lockIconViewController: LockIconViewController
+ @Mock private lateinit var lockIconViewController: LegacyLockIconViewController
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
@@ -177,7 +179,8 @@
dockManager,
notificationShadeDepthController,
view,
- notificationPanelViewController,
+ shadeViewController,
+ panelExpansionInteractor,
ShadeExpansionStateManager(),
stackScrollLayoutController,
statusBarKeyguardViewManager,
@@ -263,7 +266,7 @@
testScope.runTest {
underTest.setStatusBarViewController(phoneStatusBarViewController)
whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
.thenReturn(true)
whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
@@ -282,7 +285,7 @@
whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
.thenReturn(true)
// Item we're testing
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+ whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(false)
val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
@@ -295,7 +298,7 @@
testScope.runTest {
underTest.setStatusBarViewController(phoneStatusBarViewController)
whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
// Item we're testing
whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
.thenReturn(false)
@@ -310,7 +313,7 @@
fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
testScope.runTest {
underTest.setStatusBarViewController(phoneStatusBarViewController)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
.thenReturn(true)
// Item we're testing
@@ -327,7 +330,7 @@
testScope.runTest {
underTest.setStatusBarViewController(phoneStatusBarViewController)
whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true)
whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
.thenReturn(true)
@@ -486,7 +489,7 @@
// AND bouncer is not showing
whenever(centralSurfaces.isBouncerShowing()).thenReturn(false)
// AND panel view controller wants it
- whenever(notificationPanelViewController.handleExternalInterceptTouch(DOWN_EVENT))
+ whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT))
.thenReturn(true)
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 2ecca2e..98a815c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -22,7 +22,7 @@
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.LockIconViewController
+import com.android.keyguard.LegacyLockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
@@ -38,6 +38,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -89,7 +90,8 @@
@Mock private lateinit var dozeServiceHost: DozeServiceHost
@Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var dockManager: DockManager
- @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
@Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@@ -101,7 +103,7 @@
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
- @Mock private lateinit var lockIconViewController: LockIconViewController
+ @Mock private lateinit var lockIconViewController: LegacyLockIconViewController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var shadeLogger: ShadeLogger
@@ -166,7 +168,8 @@
dockManager,
notificationShadeDepthController,
underTest,
- notificationPanelViewController,
+ shadeViewController,
+ panelExpansionInteractor,
ShadeExpansionStateManager(),
notificationStackScrollLayoutController,
statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index b16f412..ad4b4fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -43,6 +44,7 @@
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -285,16 +287,43 @@
}
@Test
- public void updateQsState_fullscreenTrue() {
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void updateExpansion_partiallyExpanded_fullscreenFalse() {
+ // WHEN QS are only partially expanded
mQsController.setExpanded(true);
- mQsController.updateQsState();
+ when(mQs.getDesiredHeight()).thenReturn(123);
+ mQsController.setQs(mQs);
+ mQsController.onHeightChanged();
+ mQsController.setExpansionHeight(100);
+
+ // THEN they are not full screen
+ mQsController.updateExpansion();
+ assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isFalse();
+ }
+
+ @Test
+ public void updateExpansion_fullyExpanded_fullscreenTrue() {
+ // WHEN QS are fully expanded
+ mQsController.setExpanded(true);
+ when(mQs.getDesiredHeight()).thenReturn(123);
+ mQsController.setQs(mQs);
+ mQsController.onHeightChanged();
+ mQsController.setExpansionHeight(123);
+
+ // THEN they are full screen
assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isTrue();
}
@Test
- public void updateQsState_fullscreenFalse() {
+ public void updateExpansion_notExpanded_fullscreenFalse() {
+ // WHEN QS are not expanded
mQsController.setExpanded(false);
- mQsController.updateQsState();
+ when(mQs.getDesiredHeight()).thenReturn(123);
+ mQsController.setQs(mQs);
+ mQsController.onHeightChanged();
+ mQsController.setExpansionHeight(0);
+
+ // THEN they are not full screen
assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
index 15c04eb..89ae42f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeExpansionStateManagerTest.kt
@@ -42,17 +42,11 @@
val tracking = true
val dragDownAmount = 1234f
- shadeExpansionStateManager.onPanelExpansionChanged(
- fraction,
- expanded,
- tracking,
- dragDownAmount
- )
+ shadeExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking)
assertThat(listener.fraction).isEqualTo(fraction)
assertThat(listener.expanded).isEqualTo(expanded)
assertThat(listener.tracking).isEqualTo(tracking)
- assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
}
@Test
@@ -61,12 +55,7 @@
val expanded = true
val tracking = true
val dragDownAmount = 1234f
- shadeExpansionStateManager.onPanelExpansionChanged(
- fraction,
- expanded,
- tracking,
- dragDownAmount
- )
+ shadeExpansionStateManager.onPanelExpansionChanged(fraction, expanded, tracking)
val listener = TestShadeExpansionListener()
val currentState = shadeExpansionStateManager.addExpansionListener(listener)
@@ -75,7 +64,6 @@
assertThat(listener.fraction).isEqualTo(fraction)
assertThat(listener.expanded).isEqualTo(expanded)
assertThat(listener.tracking).isEqualTo(tracking)
- assertThat(listener.dragDownAmountPx).isEqualTo(dragDownAmount)
}
@Test
@@ -100,8 +88,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 0.5f,
expanded = true,
- tracking = false,
- dragDownPxAmount = 0f
+ tracking = false
)
assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -115,8 +102,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 0.5f,
expanded = true,
- tracking = true,
- dragDownPxAmount = 0f
+ tracking = true
)
assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -132,8 +118,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 0.5f,
expanded = false,
- tracking = false,
- dragDownPxAmount = 0f
+ tracking = false
)
assertThat(listener.state).isEqualTo(STATE_CLOSED)
@@ -149,8 +134,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 0.5f,
expanded = false,
- tracking = true,
- dragDownPxAmount = 0f
+ tracking = true
)
assertThat(listener.state).isEqualTo(STATE_OPEN)
@@ -166,8 +150,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 1f,
expanded = true,
- tracking = false,
- dragDownPxAmount = 0f
+ tracking = false
)
assertThat(listener.previousState).isEqualTo(STATE_OPENING)
@@ -182,8 +165,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 1f,
expanded = true,
- tracking = true,
- dragDownPxAmount = 0f
+ tracking = true
)
assertThat(listener.state).isEqualTo(STATE_OPENING)
@@ -199,8 +181,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 1f,
expanded = false,
- tracking = false,
- dragDownPxAmount = 0f
+ tracking = false
)
assertThat(listener.state).isEqualTo(STATE_CLOSED)
@@ -216,8 +197,7 @@
shadeExpansionStateManager.onPanelExpansionChanged(
fraction = 1f,
expanded = false,
- tracking = true,
- dragDownPxAmount = 0f
+ tracking = true
)
assertThat(listener.state).isEqualTo(STATE_OPEN)
@@ -229,13 +209,11 @@
var fraction: Float = 0f
var expanded: Boolean = false
var tracking: Boolean = false
- var dragDownAmountPx: Float = 0f
override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
this.fraction = event.fraction
this.expanded = event.expanded
this.tracking = event.tracking
- this.dragDownAmountPx = event.dragDownPxAmount
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index dea905a..f2abb90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -3,23 +3,18 @@
import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.FakeSceneDataSource
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -27,15 +22,8 @@
import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,9 +62,7 @@
sceneInteractor = kosmos.sceneInteractor
fakeSceneDataSource = kosmos.fakeSceneDataSource
underTest = ScrimShadeTransitionController(
- applicationScope,
shadeExpansionStateManager,
- { panelExpansionInteractor },
dumpManager,
scrimController,
)
@@ -99,96 +85,20 @@
verify(scrimController).setRawPanelExpansionFraction(DEFAULT_EXPANSION_EVENT.fraction)
}
- @Test
- @EnableSceneContainer
- fun sceneChanges_forwardsToScrimTransitionController() =
- testScope.runTest {
- var rawExpansion: Float? = null
- whenever(scrimController.setRawPanelExpansionFraction(any())).then {
- (it.arguments[0] as Float?).also { rawExp -> rawExpansion = rawExp }
- }
- setUnlocked(true)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(Scenes.Gone)
- )
- sceneInteractor.setTransitionState(transitionState)
-
- changeScene(Scenes.Gone, transitionState)
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- Truth.assertThat(currentScene).isEqualTo(Scenes.Gone)
-
- Truth.assertThat(rawExpansion)
- .isEqualTo(0f)
-
- changeScene(Scenes.Shade, transitionState) { progress ->
- Truth.assertThat(rawExpansion)
- .isEqualTo(progress)
- }
- }
-
private fun startLegacyPanelExpansion() {
shadeExpansionStateManager.onPanelExpansionChanged(
DEFAULT_EXPANSION_EVENT.fraction,
DEFAULT_EXPANSION_EVENT.expanded,
DEFAULT_EXPANSION_EVENT.tracking,
- DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
)
}
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- Truth.assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
- private fun TestScope.changeScene(
- toScene: SceneKey,
- transitionState: MutableStateFlow<ObservableTransitionState>,
- assertDuringProgress: ((progress: Float) -> Unit) = {},
- ) {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val progressFlow = MutableStateFlow(0f)
- transitionState.value =
- ObservableTransitionState.Transition(
- fromScene = checkNotNull(currentScene),
- toScene = toScene,
- progress = progressFlow,
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 0.2f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 0.6f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- progressFlow.value = 1f
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- transitionState.value = ObservableTransitionState.Idle(toScene)
- fakeSceneDataSource.changeScene(toScene)
- runCurrent()
- assertDuringProgress(progressFlow.value)
-
- Truth.assertThat(currentScene).isEqualTo(toScene)
- }
-
companion object {
val DEFAULT_EXPANSION_EVENT =
ShadeExpansionChangeEvent(
fraction = 0.5f,
expanded = true,
- tracking = true,
- dragDownPxAmount = 10f
+ tracking = true
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 68bc72b..fc0c85e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -23,18 +23,18 @@
import android.view.View
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
@@ -142,7 +142,7 @@
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -150,7 +150,7 @@
fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 0.01f, expanded = false, tracking = false, dragDownPxAmount = 0f))
+ fraction = 0.01f, expanded = false, tracking = false))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -160,7 +160,7 @@
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 0f, expanded = false, tracking = false, dragDownPxAmount = 0f))
+ fraction = 0f, expanded = false, tracking = false))
verify(shadeAnimation).animateTo(eq(0))
}
@@ -168,7 +168,7 @@
fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
val event =
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f)
+ fraction = 1f, expanded = true, tracking = false)
onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -189,7 +189,7 @@
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 0.6f, expanded = true, tracking = true, dragDownPxAmount = 0f))
+ fraction = 0.6f, expanded = true, tracking = true))
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -197,7 +197,7 @@
fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
val event =
ShadeExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 0f)
+ fraction = 0.5f, expanded = true, tracking = true)
notificationShadeDepthController.panelPullDownMinFraction = 0.5f
notificationShadeDepthController.onPanelExpansionChanged(event)
assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
@@ -225,7 +225,7 @@
notificationShadeDepthController.qsPanelExpansion = 1f
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@@ -236,7 +236,7 @@
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController)
.setNotificationShadeZoom(eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
@@ -248,7 +248,7 @@
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(0f)
@@ -260,7 +260,7 @@
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(floatThat { it > 0 })
@@ -273,7 +273,7 @@
val expanded = true
val tracking = false
val dragDownPxAmount = 0f
- val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking, dragDownPxAmount)
+ val event = ShadeExpansionChangeEvent(rawFraction, expanded, tracking)
val inOrder = Mockito.inOrder(wallpaperController)
notificationShadeDepthController.onPanelExpansionChanged(event)
@@ -338,7 +338,7 @@
fun updateBlurCallback_setsBlur_whenExpanded() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -348,7 +348,7 @@
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -367,7 +367,7 @@
fun ignoreBlurForUnlock_ignores() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -384,7 +384,7 @@
fun ignoreBlurForUnlock_doesNotIgnore() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -416,7 +416,7 @@
// And shade is blurred
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false, dragDownPxAmount = 0f))
+ fraction = 1f, expanded = true, tracking = false))
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
new file mode 100644
index 0000000..9c59f9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.telephonyManager
+import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class OperatorNameViewControllerTest : SysuiTestCase() {
+ private lateinit var underTest: OperatorNameViewController
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+
+ private val kosmos = Kosmos()
+ private val testScope = TestScope()
+
+ private val view = OperatorNameView(mContext)
+ private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+
+ @Mock private lateinit var darkIconDispatcher: DarkIconDispatcher
+ @Mock private lateinit var tunerService: TunerService
+ private var telephonyManager = kosmos.telephonyManager
+ private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+ private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val mobileConnectionsRepository = FakeMobileConnectionsRepository()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ mobileConnectionsRepository,
+ )
+
+ underTest =
+ OperatorNameViewController.Factory(
+ darkIconDispatcher,
+ tunerService,
+ telephonyManager,
+ keyguardUpdateMonitor,
+ carrierConfigTracker,
+ airplaneModeInteractor,
+ subscriptionManagerProxy,
+ javaAdapter,
+ )
+ .create(view)
+ }
+
+ @Test
+ fun updateFromSubInfo_showsCarrieName() =
+ testScope.runTest {
+ whenever(telephonyManager.isDataCapable).thenReturn(true)
+
+ val mockSubInfo =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(1)
+ whenever(it.carrierName).thenReturn("test_carrier")
+ }
+ whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+ .thenReturn(mockSubInfo)
+ whenever(keyguardUpdateMonitor.getSimState(any()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+ whenever(keyguardUpdateMonitor.getServiceState(any()))
+ .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+ subscriptionManagerProxy.defaultDataSubId = 1
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ underTest.onViewAttached()
+ runCurrent()
+
+ assertThat(view.text).isEqualTo("test_carrier")
+ }
+
+ @Test
+ fun notDataCapable_doesNotShowOperatorName() =
+ testScope.runTest {
+ whenever(telephonyManager.isDataCapable).thenReturn(false)
+
+ val mockSubInfo =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(1)
+ whenever(it.carrierName).thenReturn("test_carrier")
+ }
+ whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+ .thenReturn(mockSubInfo)
+ whenever(keyguardUpdateMonitor.getSimState(any()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+ whenever(keyguardUpdateMonitor.getServiceState(any()))
+ .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+ subscriptionManagerProxy.defaultDataSubId = 1
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ underTest.onViewAttached()
+ runCurrent()
+
+ assertTrue(view.text.isNullOrEmpty())
+ }
+
+ @Test
+ fun airplaneMode_doesNotShowOperatorName() =
+ testScope.runTest {
+ whenever(telephonyManager.isDataCapable).thenReturn(false)
+ val mockSubInfo =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(1)
+ whenever(it.carrierName).thenReturn("test_carrier")
+ }
+ whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+ .thenReturn(mockSubInfo)
+ whenever(keyguardUpdateMonitor.getSimState(any()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+ whenever(keyguardUpdateMonitor.getServiceState(any()))
+ .thenReturn(ServiceState().also { it.state = ServiceState.STATE_IN_SERVICE })
+ subscriptionManagerProxy.defaultDataSubId = 1
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ underTest.onViewAttached()
+ runCurrent()
+
+ assertTrue(view.text.isNullOrEmpty())
+ }
+
+ @Test
+ fun notInService_doesNotShowOperatorName() =
+ testScope.runTest {
+ // Data capable
+ whenever(telephonyManager.isDataCapable).thenReturn(true)
+
+ // Valid subscription
+ val mockSubInfo =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(1)
+ whenever(it.carrierName).thenReturn("test_carrier")
+ }
+ whenever(keyguardUpdateMonitor.getSubscriptionInfoForSubId(any()))
+ .thenReturn(mockSubInfo)
+ whenever(keyguardUpdateMonitor.getSimState(any()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+
+ // Not in service
+ whenever(keyguardUpdateMonitor.getServiceState(any()))
+ .thenReturn(ServiceState().also { it.state = ServiceState.STATE_OUT_OF_SERVICE })
+ // Subscription is default for data
+ subscriptionManagerProxy.defaultDataSubId = 1
+ // Not airplane mode
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ underTest.onViewAttached()
+ runCurrent()
+
+ assertTrue(view.text.isNullOrEmpty())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index a5f3f57..5abad61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -180,6 +181,7 @@
private lateinit var dateSmartspaceView: SmartspaceView
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView
+ private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@@ -225,6 +227,14 @@
setAllowPrivateNotifications(userHandleSecondary, true)
setShowNotifications(userHandlePrimary, true)
+ // Use the real wakefulness lifecycle instead of a mock
+ wakefulnessLifecycle = WakefulnessLifecycle(
+ context,
+ /* wallpaper= */ null,
+ clock,
+ dumpManager
+ )
+
controller = LockscreenSmartspaceController(
context,
featureFlags,
@@ -240,6 +250,7 @@
deviceProvisionedController,
keyguardBypassController,
keyguardUpdateMonitor,
+ wakefulnessLifecycle,
dumpManager,
execution,
executor,
@@ -773,6 +784,38 @@
verify(configurationController, never()).addCallback(any())
}
+ @Test
+ fun testWakefulnessLifecycleDispatch_wake_setsSmartspaceScreenOnTrue() {
+ // Connect session
+ connectSession()
+
+ // Add mock views
+ val mockSmartspaceView = mock(SmartspaceView::class.java)
+ controller.smartspaceViews.add(mockSmartspaceView)
+
+ // Initiate wakefulness change
+ wakefulnessLifecycle.dispatchStartedWakingUp(0)
+
+ // Verify smartspace views receive screen on
+ verify(mockSmartspaceView).setScreenOn(true)
+ }
+
+ @Test
+ fun testWakefulnessLifecycleDispatch_sleep_setsSmartspaceScreenOnFalse() {
+ // Connect session
+ connectSession()
+
+ // Add mock views
+ val mockSmartspaceView = mock(SmartspaceView::class.java)
+ controller.smartspaceViews.add(mockSmartspaceView)
+
+ // Initiate wakefulness change
+ wakefulnessLifecycle.dispatchFinishedGoingToSleep()
+
+ // Verify smartspace views receive screen on
+ verify(mockSmartspaceView).setScreenOn(false)
+ }
+
private fun connectSession() {
val dateView = controller.buildAndConnectDateView(fakeParent)
dateSmartspaceView = dateView as SmartspaceView
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 82093ad..67b540c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -19,10 +19,15 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.communal.data.repository.communalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dump.DumpManager
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
@@ -34,11 +39,16 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -49,6 +59,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -56,7 +67,8 @@
@get:Rule val animatorTestRule = AnimatorTestRule(this)
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
@@ -97,6 +109,7 @@
whenever(statusBarStateController.state).then { statusBarState }
notificationWakeUpCoordinator =
NotificationWakeUpCoordinator(
+ kosmos.applicationCoroutineScope,
dumpManager,
headsUpManager,
statusBarStateController,
@@ -105,6 +118,7 @@
screenOffAnimationController,
logger,
kosmos.notificationsKeyguardInteractor,
+ kosmos.communalInteractor,
)
statusBarStateCallback = withArgCaptor {
verify(statusBarStateController).addCallback(capture())
@@ -161,6 +175,39 @@
}
@Test
+ fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(CommunalScenes.Communal)
+ )
+ kosmos.communalRepository.setTransitionState(transitionState)
+ runCurrent()
+ setDozeAmount(0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+ }
+
+ @Test
+ fun closingCommunalWillShowNotifications() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(CommunalScenes.Communal)
+ )
+ kosmos.communalRepository.setTransitionState(transitionState)
+ runCurrent()
+ setDozeAmount(0f)
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+ transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Blank)
+ runCurrent()
+ verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
+ assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+ }
+
+ @Test
fun switchingToShadeWithBypassEnabledWillShowNotifications() {
setDozeToZeroWithBypassWillFullyHideNotifications()
clearInvocations(stackScrollerController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index 85b8b03..d3df48e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -98,6 +98,7 @@
`when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
`when`(rebuilder.rebuildForRemoteInputReply(any())).thenReturn(sbn)
`when`(rebuilder.rebuildForSendingSmartReply(any(), any())).thenReturn(sbn)
+ `when`(rebuilder.rebuildWithExistingReplies(any())).thenReturn(sbn)
}
val remoteInputActiveExtender get() = coordinator.mRemoteInputActiveExtender
@@ -208,13 +209,30 @@
it.onEntryUpdated(entry, true)
}
-
verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry)
verify(smartReplyController, times(1)).stopSending(entry)
}
@Test
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+ fun testRepeatedUpdateTriggersRebuild() {
+ // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+ val entry = NotificationEntryBuilder()
+ .setId(3)
+ .setTag("entry")
+ .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+ .build()
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+ collectionListeners.forEach {
+ it.onEntryUpdated(entry, true)
+ }
+
+ verify(rebuilder, times(1)).rebuildWithExistingReplies(entry)
+ }
+
+ @Test
+ @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testLifetimeExtensionListenerClearsRemoteInputs() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
val entry = NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 24195fe..4eb7daa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -109,7 +109,6 @@
testComponent.apply {
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
- deviceProvisioningRepository.setFactoryResetProtectionActive(false)
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.OTHER,
@@ -120,20 +119,6 @@
}
@Test
- fun animationsEnabled_isFalse_whenFrpIsActive() =
- testComponent.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- )
- )
- val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
- }
-
- @Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
testComponent.runTest {
powerRepository.updateWakefulness(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index c40401f..35b8493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -117,7 +117,6 @@
fun setup() {
testComponent.apply {
keyguardRepository.setKeyguardShowing(false)
- deviceProvisioningRepository.setFactoryResetProtectionActive(false)
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.OTHER,
@@ -127,20 +126,6 @@
}
@Test
- fun animationsEnabled_isFalse_whenFrpIsActive() =
- testComponent.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- )
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
- }
-
- @Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
testComponent.runTest {
powerRepository.updateWakefulness(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 0e89d80..06a4d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -59,7 +59,8 @@
import com.android.internal.widget.CachingIconView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -90,13 +91,14 @@
@RunWithLooper
public class ExpandableNotificationRowTest extends SysuiTestCase {
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
private NotificationTestHelper mNotificationTestHelper;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
mNotificationTestHelper = new NotificationTestHelper(
mContext,
mDependency,
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 dd53474..ed29665 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
@@ -1035,15 +1035,6 @@
verify(mStatusBarStateController).setState(SHADE);
}
- @Test
- public void frpLockedDevice_shadeDisabled() {
- when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
- when(mDozeServiceHost.isPulsing()).thenReturn(true);
- mCentralSurfaces.updateNotificationPanelTouchState();
-
- verify(mNotificationPanelViewController).setTouchAndAnimationDisabled(true);
- }
-
/** Regression test for b/298355063 */
@Test
public void fingerprintManagerNull_noNPE() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 054680d..34605fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -597,8 +597,7 @@
private static ShadeExpansionChangeEvent expansionEvent(
float fraction, boolean expanded, boolean tracking) {
- return new ShadeExpansionChangeEvent(
- fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+ return new ShadeExpansionChangeEvent(fraction, expanded, tracking);
}
@Test
@@ -607,7 +606,7 @@
/* verify that a predictive back callback is registered when the bouncer becomes visible */
mBouncerExpansionCallback.onVisibilityChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mBackCallbackCaptor.capture());
/* verify that the same callback is unregistered when the bouncer becomes invisible */
@@ -622,7 +621,7 @@
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mBackCallbackCaptor.capture());
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
@@ -642,7 +641,7 @@
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mBackCallbackCaptor.capture());
assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
@@ -660,7 +659,7 @@
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
mBackCallbackCaptor.capture());
assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 3da5ab9..9c3d9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -52,7 +52,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
@@ -115,7 +115,7 @@
@Mock
private HeadsUpAppearanceController mHeadsUpAppearanceController;
@Mock
- private ShadeViewController mShadeViewController;
+ private PanelExpansionInteractor mPanelExpansionInteractor;
@Mock
private StatusBarIconController.DarkIconManager.Factory mIconManagerFactory;
@Mock
@@ -304,7 +304,7 @@
// WHEN the shade is open and configured to hide the status bar icons
mShadeExpansionStateManager.updateState(STATE_OPEN);
- when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+ when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -320,7 +320,7 @@
// WHEN the shade is open but *not* configured to hide the status bar icons
mShadeExpansionStateManager.updateState(STATE_OPEN);
- when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
+ when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -337,7 +337,7 @@
// WHEN the shade is open and configured to hide the status bar icons
mShadeExpansionStateManager.updateState(STATE_OPEN);
- when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+ when(mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -696,7 +696,7 @@
mCollapsedStatusBarViewBinder,
mStatusBarHideIconsForBouncerManager,
mKeyguardStateController,
- mShadeViewController,
+ mPanelExpansionInteractor,
mStatusBarStateController,
mock(NotificationIconContainerStatusBarViewBinder.class),
mCommandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 361fa5b..31bd57e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -86,7 +86,6 @@
globalSettings,
userTracker,
dumpManager,
- buildInfo,
Handler(testableLooper.looper),
mainExecutor
)
@@ -99,12 +98,6 @@
}
@Test
- fun testFrpNotActiveByDefault() {
- init()
- assertThat(controller.isFrpActive).isFalse()
- }
-
- @Test
fun testNotUserSetupByDefault() {
init()
assertThat(controller.isUserSetup(START_USER)).isFalse()
@@ -119,14 +112,6 @@
}
@Test
- fun testFrpActiveWhenCreated() {
- globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
- init()
-
- assertThat(controller.isFrpActive).isTrue()
- }
-
- @Test
fun testUserSetupWhenCreated() {
secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
init()
@@ -145,16 +130,6 @@
}
@Test
- fun testFrpActiveChange() {
- init()
-
- globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
- testableLooper.processAllMessages() // background observer
-
- assertThat(controller.isFrpActive).isTrue()
- }
-
- @Test
fun testUserSetupChange() {
init()
@@ -197,7 +172,6 @@
mainExecutor.runAllReady()
verify(listener, never()).onDeviceProvisionedChanged()
- verify(listener, never()).onFrpActiveChanged()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onUserSwitched()
}
@@ -215,7 +189,6 @@
verify(listener).onUserSwitched()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onDeviceProvisionedChanged()
- verify(listener, never()).onFrpActiveChanged()
}
@Test
@@ -230,7 +203,6 @@
verify(listener, never()).onUserSwitched()
verify(listener).onUserSetupChanged()
verify(listener, never()).onDeviceProvisionedChanged()
- verify(listener, never()).onFrpActiveChanged()
}
@Test
@@ -244,26 +216,10 @@
verify(listener, never()).onUserSwitched()
verify(listener, never()).onUserSetupChanged()
- verify(listener, never()).onFrpActiveChanged()
verify(listener).onDeviceProvisionedChanged()
}
@Test
- fun testListenerCalledOnFrpActiveChanged() {
- init()
- controller.addCallback(listener)
-
- globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
- testableLooper.processAllMessages()
- mainExecutor.runAllReady()
-
- verify(listener, never()).onUserSwitched()
- verify(listener, never()).onUserSetupChanged()
- verify(listener, never()).onDeviceProvisionedChanged()
- verify(listener).onFrpActiveChanged()
- }
-
- @Test
fun testRemoveListener() {
init()
controller.addCallback(listener)
@@ -272,13 +228,11 @@
switchUser(10)
secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
- globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1)
testableLooper.processAllMessages()
mainExecutor.runAllReady()
verify(listener, never()).onDeviceProvisionedChanged()
- verify(listener, never()).onFrpActiveChanged()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onUserSwitched()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index c259782..70afbd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -67,10 +67,10 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -448,6 +448,34 @@
assertEquals(1f, fadeInView.getAlpha());
}
+ @Test
+ public void testUnanimatedFocusAfterDefocusAnimation() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ bindController(view, row.getEntry());
+
+ FrameLayout parent = new FrameLayout(mContext);
+ parent.addView(view);
+
+ // Play defocus animation
+ view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */);
+ mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+ // assert that RemoteInputView is no longer visible, but alpha is reset to 1f
+ assertEquals(View.GONE, view.getVisibility());
+ assertEquals(1f, view.getAlpha());
+
+ // focus RemoteInputView without an animation
+ view.focus();
+ // assert that RemoteInputView is visible, and alpha is 1f
+ assertEquals(View.VISIBLE, view.getVisibility());
+ assertEquals(1f, view.getAlpha());
+ }
+
// NOTE: because we're refactoring the RemoteInputView and moving logic into the
// RemoteInputViewController, it's easiest to just test the system of the two classes together.
@NonNull
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 867476f..581ca3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -28,6 +28,7 @@
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
+import android.os.UserHandle
import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
@@ -85,7 +86,6 @@
@Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var mediaProjectionManager: MediaProjectionManager
@Mock private lateinit var packageManager: PackageManager
- @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
@Mock private lateinit var listener1: Runnable
@Mock private lateinit var listener2: Runnable
@Mock private lateinit var listener3: Runnable
@@ -95,6 +95,7 @@
private lateinit var globalSettings: FakeGlobalSettings
private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback
private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+ private lateinit var mediaProjectionInfo: MediaProjectionInfo
@Before
fun setUp() {
@@ -109,14 +110,29 @@
setShareFullScreen()
whenever(activityManager.bugreportWhitelistedPackages)
.thenReturn(listOf(BUGREPORT_PACKAGE_NAME))
- whenever(packageManager.getPackageUid(TEST_PROJECTION_PACKAGE_NAME, 0))
+ whenever(
+ packageManager.getPackageUidAsUser(
+ TEST_PROJECTION_PACKAGE_NAME,
+ UserHandle.CURRENT.identifier
+ )
+ )
.thenReturn(TEST_PROJECTION_PACKAGE_UID)
- whenever(packageManager.getPackageUid(BUGREPORT_PACKAGE_NAME, 0))
+ whenever(
+ packageManager.getPackageUidAsUser(
+ BUGREPORT_PACKAGE_NAME,
+ UserHandle.CURRENT.identifier
+ )
+ )
.thenReturn(BUGREPORT_PACKAGE_UID)
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to
// return the correct uid in this scenario
- whenever(packageManager.getPackageUid(mContext.packageName, 0))
+ whenever(
+ packageManager.getPackageUidAsUser(
+ mContext.packageName,
+ UserHandle.CURRENT.identifier
+ )
+ )
.thenReturn(mContext.applicationInfo.uid)
whenever(packageManager.checkPermission(anyString(), anyString()))
@@ -271,7 +287,7 @@
fun isSensitiveStateActive_projectionActive_sysuiExempt_false() {
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding
- whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ setShareFullScreenViaSystemUi()
mediaProjectionCallback.onStart(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
@@ -309,7 +325,7 @@
@Test
fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() {
- whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ setShareFullScreenViaBugReportHandler()
mediaProjectionCallback.onStart(mediaProjectionInfo)
assertFalse(controller.isSensitiveStateActive)
@@ -371,7 +387,7 @@
fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding
- whenever(mediaProjectionInfo.packageName).thenReturn(mContext.packageName)
+ setShareFullScreenViaSystemUi()
mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
@@ -415,7 +431,7 @@
@Test
fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() {
- whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
+ setShareFullScreenViaBugReportHandler()
mediaProjectionCallback.onStart(mediaProjectionInfo)
val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
@@ -548,9 +564,7 @@
fun logSensitiveContentProtectionSession_exemptViaSystemUi() {
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding
- val testPackageName = mContext.packageName
- val testUid = mContext.applicationInfo.uid
- whenever(mediaProjectionInfo.packageName).thenReturn(testPackageName)
+ setShareFullScreenViaSystemUi()
mediaProjectionCallback.onStart(mediaProjectionInfo)
@@ -558,7 +572,7 @@
FrameworkStatsLog.write(
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
anyLong(),
- eq(testUid),
+ eq(mContext.applicationInfo.uid),
eq(true),
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START),
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
@@ -571,7 +585,7 @@
FrameworkStatsLog.write(
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION),
anyLong(),
- eq(testUid),
+ eq(mContext.applicationInfo.uid),
eq(true),
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP),
eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI)
@@ -582,8 +596,7 @@
@Test
fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() {
// Setup exempt via bugreport handler
- whenever(mediaProjectionInfo.packageName).thenReturn(BUGREPORT_PACKAGE_NAME)
-
+ setShareFullScreenViaBugReportHandler()
mediaProjectionCallback.onStart(mediaProjectionInfo)
verify {
@@ -619,13 +632,26 @@
}
private fun setShareFullScreen() {
- whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- whenever(mediaProjectionInfo.launchCookie).thenReturn(null)
+ setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true)
+ }
+
+ private fun setShareFullScreenViaBugReportHandler() {
+ setShareScreen(BUGREPORT_PACKAGE_NAME, true)
+ }
+
+ private fun setShareFullScreenViaSystemUi() {
+ // SystemUi context package name is exempt, but in test scenarios its
+ // com.android.systemui.tests so use that instead of hardcoding
+ setShareScreen(mContext.packageName, true)
}
private fun setShareSingleApp() {
- whenever(mediaProjectionInfo.packageName).thenReturn(TEST_PROJECTION_PACKAGE_NAME)
- whenever(mediaProjectionInfo.launchCookie).thenReturn(ActivityOptions.LaunchCookie())
+ setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false)
+ }
+
+ private fun setShareScreen(packageName: String, fullScreen: Boolean) {
+ val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie()
+ mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie)
}
private fun setupNotificationEntry(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
index 12694ae..21ed384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/DeviceProvisioningRepositoryImplTest.kt
@@ -78,31 +78,4 @@
.onDeviceProvisionedChanged()
assertThat(deviceProvisioned).isFalse()
}
-
- @Test
- fun isFrpActive_reflectsCurrentControllerState() = runTest {
- whenever(deviceProvisionedController.isFrpActive).thenReturn(true)
- val frpActive by collectLastValue(underTest.isFactoryResetProtectionActive)
- assertThat(frpActive).isTrue()
- }
-
- @Test
- fun isFrpActive_updatesWhenControllerStateChanges_toTrue() = runTest {
- val frpActive by collectLastValue(underTest.isFactoryResetProtectionActive)
- runCurrent()
- whenever(deviceProvisionedController.isFrpActive).thenReturn(true)
- withArgCaptor { verify(deviceProvisionedController).addCallback(capture()) }
- .onFrpActiveChanged()
- assertThat(frpActive).isTrue()
- }
-
- @Test
- fun isFrpActive_updatesWhenControllerStateChanges_toFalse() = runTest {
- val frpActive by collectLastValue(underTest.isFactoryResetProtectionActive)
- runCurrent()
- whenever(deviceProvisionedController.isFrpActive).thenReturn(false)
- withArgCaptor { verify(deviceProvisionedController).addCallback(capture()) }
- .onFrpActiveChanged()
- assertThat(frpActive).isFalse()
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 1b43851..3dee093 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -1014,6 +1014,136 @@
verify(spyContext, never()).startServiceAsUser(any(), any())
}
+ @Test
+ fun userIsAdminAndRestricted_addUserActionsNotAdded() {
+ createUserInteractor()
+ testScope.runTest {
+ val id = 0
+ val userInfo =
+ UserInfo(
+ id,
+ "child",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_ADMIN,
+ UserManager.USER_TYPE_FULL_RESTRICTED,
+ )
+ whenever(
+ manager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(id)
+ )
+ )
+ .thenReturn(true)
+
+ userRepository.setUserInfos(listOf(userInfo))
+ userRepository.setSelectedUserInfo(userInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val value = collectLastValue(underTest.actions)
+ runCurrent()
+
+ assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+ }
+ }
+
+ @Test
+ fun userIsNotRestrictedAndCannotAddGuests_actionsDoesNotIncludeAddGuest() {
+ createUserInteractor()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+
+ whenever(manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)).thenReturn(false)
+
+ val value = collectLastValue(underTest.actions)
+ runCurrent()
+
+ assertThat(value())
+ .isEqualTo(
+ listOf(
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun userIsNotRestrictedAndCannotAddUsers_actionsDoesNotIncludeAddUsers() {
+ createUserInteractor()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+
+ whenever(manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY))
+ .thenReturn(false)
+
+ val value = collectLastValue(underTest.actions)
+ runCurrent()
+
+ assertThat(value())
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun systemUserHasRestrictions_addUserActionsNotAdded() {
+ createUserInteractor()
+ testScope.runTest {
+ val systemId = 0
+ val systemUser =
+ UserInfo(
+ systemId,
+ "system",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_SYSTEM,
+ UserManager.USER_TYPE_SYSTEM_HEADLESS,
+ )
+ val adminId = 10
+ val adminUser =
+ UserInfo(
+ adminId,
+ "admin",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_ADMIN,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ )
+
+ userRepository.setUserInfos(listOf(systemUser, adminUser))
+ userRepository.setSelectedUserInfo(adminUser)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+
+ whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
+ whenever(
+ manager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(0)
+ )
+ )
+ .thenReturn(true)
+
+ val value = collectLastValue(underTest.actions)
+ runCurrent()
+
+ assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+ }
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index fccb936..dc5597a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
+import android.app.role.RoleManager;
import android.content.Intent;
import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -54,11 +55,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class QuickAccessWalletControllerTest extends SysuiTestCase {
+ private static final String WALLET_ROLE_HOLDER = "wallet.role.holder";
@Mock
private QuickAccessWalletClient mQuickAccessWalletClient;
@Mock
@@ -69,6 +73,8 @@
private ActivityStarter mActivityStarter;
@Mock
private ActivityTransitionAnimator.Controller mAnimationController;
+ @Mock
+ private RoleManager mRoleManager;
@Captor
private ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
@Captor
@@ -102,7 +108,8 @@
MoreExecutors.directExecutor(),
mSecureSettings,
mQuickAccessWalletClient,
- mClock);
+ mClock,
+ mRoleManager);
}
@Test
@@ -113,6 +120,24 @@
}
@Test
+ public void walletRoleAvailable_isAvailable() {
+ when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(true);
+ when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_WALLET)))
+ .thenReturn(List.of(WALLET_ROLE_HOLDER));
+
+ assertTrue(mController.isWalletRoleAvailable());
+ }
+
+ @Test
+ public void walletRoleAvailable_isNotAvailable() {
+ when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(false);
+ when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_WALLET)))
+ .thenReturn(List.of(WALLET_ROLE_HOLDER));
+
+ assertFalse(mController.isWalletRoleAvailable());
+ }
+
+ @Test
public void walletServiceUnavailable_walletNotEnabled() {
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index bc0bf9d..de7b14d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
@@ -45,6 +46,7 @@
import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -71,7 +73,10 @@
@Binds @Main fun bindMainResources(resources: Resources): Resources
@Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
- @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+
+ @Binds
+ @SysUISingleton
+ fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
@Binds
fun provideFaceAuthInteractor(
@@ -109,6 +114,15 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun providesSceneDataSourceDelegator(
+ @Application applicationScope: CoroutineScope,
+ config: SceneContainerConfig,
+ ): SceneDataSourceDelegator {
+ return SceneDataSourceDelegator(applicationScope, config)
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 5ff588f..9f5c6b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,6 +2,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.communal.shared.model.CommunalScenes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -17,11 +18,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
class FakeCommunalRepository(
applicationScope: CoroutineScope,
- override val desiredScene: MutableStateFlow<SceneKey> =
+ override val currentScene: MutableStateFlow<SceneKey> =
MutableStateFlow(CommunalScenes.Default),
) : CommunalRepository {
- override fun setDesiredScene(desiredScene: SceneKey) {
- this.desiredScene.value = desiredScene
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+ this.currentScene.value = toScene
}
private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
index c909dd6..b943298 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -21,11 +21,13 @@
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
AlternateBouncerToGoneTransitionViewModel(
bouncerToGoneFlows = bouncerToGoneFlows,
animationFlow = keyguardTransitionAnimationFlow,
+ statusBarStateController = sysuiStatusBarStateController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index b91aafe..f856d27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -62,6 +62,7 @@
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
+ occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index f0fedd2..1e25f7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.lockscreenContentViewModel by
@@ -30,5 +31,6 @@
authController = authController,
longPress = keyguardLongPressViewModel,
shadeInteractor = shadeInteractor,
+ applicationScope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
index 17c3a14..7a023ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt
@@ -19,11 +19,13 @@
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.sysuiStatusBarStateController
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
val Kosmos.lockscreenToGoneTransitionViewModel by Fixture {
LockscreenToGoneTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
+ statusBarStateController = sysuiStatusBarStateController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..a05e606
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.occludedToDozingTransitionViewModel by Fixture {
+ OccludedToDozingTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..b32960a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.sceneContainerOcclusionInteractor by Fixture {
+ SceneContainerOcclusionInteractor(
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ sceneInteractor = sceneInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 513c6ab..4a2eaf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -27,6 +27,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -78,4 +79,11 @@
{ mock<NotificationGutsManager>() },
)
}
-var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
+var Kosmos.shadeController: ShadeController by
+ Kosmos.Fixture {
+ if (SceneContainerFlag.isEnabled) {
+ shadeControllerSceneImpl
+ } else {
+ shadeControllerImpl
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index 65e04f4..b85858d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -22,13 +22,17 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.LogBuffer
-import com.android.systemui.shade.ShadeHeaderController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.shadeController
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.shade.transition.ScrimShadeTransitionController
import com.android.systemui.statusbar.policy.splitShadeStateController
import com.android.systemui.util.mockito.mock
+@Deprecated("ShadeExpansionStateManager is deprecated. Remove your dependency on it instead.")
+val Kosmos.shadeExpansionStateManager by Fixture { ShadeExpansionStateManager() }
+
val Kosmos.shadeStartable by Fixture {
ShadeStartable(
applicationScope = applicationCoroutineScope,
@@ -36,9 +40,10 @@
touchLog = mock<LogBuffer>(),
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
- controller = splitShadeStateController,
- shadeHeaderController = mock<ShadeHeaderController>(),
+ splitShadeStateController = splitShadeStateController,
scrimShadeTransitionController = mock<ScrimShadeTransitionController>(),
- shadeController = shadeController
+ sceneInteractorProvider = { sceneInteractor },
+ panelExpansionInteractorProvider = { panelExpansionInteractor },
+ shadeExpansionStateManager = shadeExpansionStateManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
index d793740..a90a9ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.domain.interactor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,10 +28,11 @@
val Kosmos.keyguardOcclusionInteractor by
Kosmos.Fixture {
KeyguardOcclusionInteractor(
- scope = testScope.backgroundScope,
+ applicationScope = testScope.backgroundScope,
repository = keyguardOcclusionRepository,
powerInteractor = powerInteractor,
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
+ deviceUnlockedInteractor = { deviceUnlockedInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
index ebcabf8..91b2956 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt
@@ -30,10 +30,6 @@
return currentUser in usersSetup
}
- override fun isFrpActive(): Boolean {
- TODO("Not yet implemented")
- }
-
fun setCurrentUser(userId: Int) {
currentUser = userId
callbacks.toSet().forEach { it.onUserSwitched() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
index fc6a800..9247e88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeDeviceProvisioningRepository.kt
@@ -26,14 +26,9 @@
class FakeDeviceProvisioningRepository @Inject constructor() : DeviceProvisioningRepository {
private val _isDeviceProvisioned = MutableStateFlow(true)
override val isDeviceProvisioned: Flow<Boolean> = _isDeviceProvisioned
- private val _isFactoryResetProtectionActive = MutableStateFlow(false)
- override val isFactoryResetProtectionActive: Flow<Boolean> = _isFactoryResetProtectionActive
fun setDeviceProvisioned(isProvisioned: Boolean) {
_isDeviceProvisioned.value = isProvisioned
}
- fun setFactoryResetProtectionActive(isActive: Boolean) {
- _isFactoryResetProtectionActive.value = isActive
- }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index a3ad2b8..4788624 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -44,6 +44,8 @@
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
+ private var isAffectedByMute: MutableMap<AudioStream, Boolean> = mutableMapOf()
+
private fun getAudioStreamModelState(
audioStream: AudioStream
): MutableStateFlow<AudioStreamModel> =
@@ -93,4 +95,15 @@
fun setLastAudibleVolume(audioStream: AudioStream, volume: Int) {
lastAudibleVolumes[audioStream] = volume
}
+
+ override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
+ mutableRingerMode.value = mode
+ }
+
+ override suspend fun isAffectedByMute(audioStream: AudioStream): Boolean =
+ isAffectedByMute[audioStream] ?: true
+
+ fun setIsAffectedByMute(audioStream: AudioStream, isAffected: Boolean) {
+ isAffectedByMute[audioStream] = isAffected
+ }
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 178102e..8905ad3 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -151,6 +151,18 @@
filegroup {
name: "ravenwood-services-jarjar-rules",
- srcs: ["ravenwood-services-jarjar-rules.txt"],
+ srcs: ["texts/ravenwood-services-jarjar-rules.txt"],
visibility: ["//frameworks/base"],
}
+
+// For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
+// The "test" just shows the available stats filenames.
+sh_test_host {
+ name: "ravenwood-stats-checker",
+ src: "scripts/ravenwood-stats-checker.sh",
+ test_suites: ["general-tests"],
+ data: [
+ ":framework-minus-apex.ravenwood.stats",
+ ":services.core.ravenwood.stats",
+ ],
+}
diff --git a/ravenwood/bulk_enable.py b/ravenwood/scripts/bulk_enable.py
similarity index 100%
rename from ravenwood/bulk_enable.py
rename to ravenwood/scripts/bulk_enable.py
diff --git a/ravenwood/fix_test_runner.py b/ravenwood/scripts/fix_test_runner.py
similarity index 100%
rename from ravenwood/fix_test_runner.py
rename to ravenwood/scripts/fix_test_runner.py
diff --git a/ravenwood/list-ravenwood-tests.sh b/ravenwood/scripts/list-ravenwood-tests.sh
similarity index 100%
rename from ravenwood/list-ravenwood-tests.sh
rename to ravenwood/scripts/list-ravenwood-tests.sh
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/scripts/ravenwood-stats-checker.sh
similarity index 65%
copy from ravenwood/run-ravenwood-tests.sh
copy to ravenwood/scripts/ravenwood-stats-checker.sh
index a303626..93f4a3f 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/ravenwood-stats-checker.sh
@@ -13,16 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Run all the ravenwood tests + hoststubgen unit tests.
-
-all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test"
-
-# "echo" is to remove the newlines
-all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
-
-run() {
- echo "Running: $*"
- "${@}"
-}
-
-run ${ATEST:-atest} $all_tests
+# Just print the available *.csv filenames.
+echo '#Stats files:'
+ls *.csv
\ No newline at end of file
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
new file mode 100755
index 0000000..4dcaa2b
--- /dev/null
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Script to collect the ravenwood "stats" CVS files and create a single file.
+
+set -e
+
+# Output file
+out=/tmp/ravenwood-stats-all.csv
+
+# Where the input files are.
+path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
+
+m() {
+ ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
+}
+
+# Building this will generate the files we need.
+m ravenwood-stats-checker
+
+# Start...
+
+cd $path
+
+dump() {
+ local jar=$1
+ local file=$2
+
+ sed -e '1d' -e "s/^/$jar,/" $file
+}
+
+collect() {
+ echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
+ dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
+ dump "service.core" hoststubgen_services.core_stats.csv
+}
+
+collect >$out
+
+echo "Full dump CVS created at $out"
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
similarity index 95%
rename from ravenwood/run-ravenwood-tests.sh
rename to ravenwood/scripts/run-ravenwood-tests.sh
index a303626..926c08f 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -15,7 +15,7 @@
# Run all the ravenwood tests + hoststubgen unit tests.
-all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test"
+all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker"
# "echo" is to remove the newlines
all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
similarity index 100%
rename from ravenwood/framework-minus-apex-ravenwood-policies.txt
rename to ravenwood/texts/framework-minus-apex-ravenwood-policies.txt
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
similarity index 100%
rename from ravenwood/ravenwood-annotation-allowed-classes.txt
rename to ravenwood/texts/ravenwood-annotation-allowed-classes.txt
diff --git a/ravenwood/ravenwood-services-jarjar-rules.txt b/ravenwood/texts/ravenwood-services-jarjar-rules.txt
similarity index 100%
rename from ravenwood/ravenwood-services-jarjar-rules.txt
rename to ravenwood/texts/ravenwood-services-jarjar-rules.txt
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
similarity index 100%
rename from ravenwood/ravenwood-standard-options.txt
rename to ravenwood/texts/ravenwood-standard-options.txt
diff --git a/ravenwood/services.core-ravenwood-policies.txt b/ravenwood/texts/services.core-ravenwood-policies.txt
similarity index 100%
rename from ravenwood/services.core-ravenwood-policies.txt
rename to ravenwood/texts/services.core-ravenwood-policies.txt
diff --git a/services/Android.bp b/services/Android.bp
index 7bbb42e..29d1acf 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -254,6 +254,7 @@
required: [
"libukey2_jni_shared",
"protolog.conf.json.gz",
+ "core.protolog.pb",
],
lint: {
baseline_filename: "lint-baseline.xml",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 21cc8da..9403796 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3550,7 +3550,7 @@
&& userState.isMagnificationTwoFingerTripleTapEnabledLocked()));
final boolean createConnectionForCurrentCapability =
- com.android.window.flags.Flags.magnificationAlwaysDrawFullscreenBorder()
+ com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
|| (userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index fb28055..1f65e15 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -37,6 +37,8 @@
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -695,6 +697,13 @@
throw new IllegalArgumentException(
bluetoothAddress + " is not a valid Bluetooth address");
}
+ final BluetoothManager bluetoothManager =
+ mContext.getSystemService(BluetoothManager.class);
+ final String bluetoothDeviceName = bluetoothManager == null ? null :
+ bluetoothManager.getAdapter().getBondedDevices().stream()
+ .filter(device -> device.getAddress().equalsIgnoreCase(bluetoothAddress))
+ .map(BluetoothDevice::getName)
+ .findFirst().orElse(null);
synchronized (mLock) {
checkAccessibilityAccessLocked();
if (mBrailleDisplayConnection != null) {
@@ -706,7 +715,10 @@
connection.setTestData(mTestBrailleDisplays);
}
connection.connectLocked(
- bluetoothAddress, BrailleDisplayConnection.BUS_BLUETOOTH, controller);
+ bluetoothAddress,
+ bluetoothDeviceName,
+ BrailleDisplayConnection.BUS_BLUETOOTH,
+ controller);
}
}
@@ -763,7 +775,10 @@
connection.setTestData(mTestBrailleDisplays);
}
connection.connectLocked(
- usbSerialNumber, BrailleDisplayConnection.BUS_USB, controller);
+ usbSerialNumber,
+ usbDevice.getProductName(),
+ BrailleDisplayConnection.BUS_USB,
+ controller);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
index 8b41873..b0da3f0 100644
--- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java
@@ -24,6 +24,7 @@
import android.accessibilityservice.IBrailleDisplayController;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.PermissionManuallyEnforced;
import android.annotation.RequiresNoPermission;
import android.bluetooth.BluetoothDevice;
@@ -33,6 +34,7 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
@@ -141,6 +143,8 @@
@BusType
int getDeviceBusType(@NonNull Path path);
+
+ String getName(@NonNull Path path);
}
/**
@@ -149,15 +153,19 @@
* <p>If found, saves instance state for this connection and starts a thread to
* read from the Braille display.
*
- * @param expectedUniqueId The expected unique ID of the device to connect, from
- * {@link UsbDevice#getSerialNumber()}
- * or {@link BluetoothDevice#getAddress()}
- * @param expectedBusType The expected bus type from {@link BusType}.
- * @param controller Interface containing oneway callbacks used to communicate with the
- * {@link android.accessibilityservice.BrailleDisplayController}.
+ * @param expectedUniqueId The expected unique ID of the device to connect, from
+ * {@link UsbDevice#getSerialNumber()} or
+ * {@link BluetoothDevice#getAddress()}.
+ * @param expectedName The expected name of the device to connect, from
+ * {@link BluetoothDevice#getName()} or
+ * {@link UsbDevice#getProductName()}.
+ * @param expectedBusType The expected bus type from {@link BusType}.
+ * @param controller Interface containing oneway callbacks used to communicate with the
+ * {@link android.accessibilityservice.BrailleDisplayController}.
*/
void connectLocked(
@NonNull String expectedUniqueId,
+ @Nullable String expectedName,
@BusType int expectedBusType,
@NonNull IBrailleDisplayController controller) {
Objects.requireNonNull(expectedUniqueId);
@@ -179,10 +187,20 @@
unableToGetDescriptor = true;
continue;
}
+ final boolean matchesIdentifier;
final String uniqueId = mScanner.getUniqueId(path);
+ if (uniqueId != null) {
+ matchesIdentifier = expectedUniqueId.equalsIgnoreCase(uniqueId);
+ } else {
+ // HIDIOCGRAWUNIQ was added in kernel version 5.7.
+ // If the device has an older kernel that does not support that ioctl then as a
+ // fallback we can check against the device name (from HIDIOCGRAWNAME).
+ final String name = mScanner.getName(path);
+ matchesIdentifier = !TextUtils.isEmpty(expectedName) && expectedName.equals(name);
+ }
if (isBrailleDisplay(descriptor)
&& mScanner.getDeviceBusType(path) == expectedBusType
- && expectedUniqueId.equalsIgnoreCase(uniqueId)) {
+ && matchesIdentifier) {
result.add(Pair.create(path.toFile(), descriptor));
}
}
@@ -498,6 +516,12 @@
Integer busType = readFromFileDescriptor(path, nativeInterface::getHidrawBusType);
return busType != null ? busType : BUS_UNKNOWN;
}
+
+ @Override
+ public String getName(@NonNull Path path) {
+ Objects.requireNonNull(path);
+ return readFromFileDescriptor(path, nativeInterface::getHidrawName);
+ }
};
}
@@ -542,6 +566,12 @@
BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH)
? BUS_BLUETOOTH : BUS_USB;
}
+
+ @Override
+ public String getName(@NonNull Path path) {
+ return brailleDisplayMap.get(path).getString(
+ BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME);
+ }
};
return mScanner;
}
@@ -579,6 +609,8 @@
* @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails.
*/
int getHidrawBusType(int fd);
+
+ String getHidrawName(int fd);
}
/** Native interface that actually calls native HIDRAW ioctls. */
@@ -602,6 +634,11 @@
public int getHidrawBusType(int fd) {
return nativeGetHidrawBusType(fd);
}
+
+ @Override
+ public String getHidrawName(int fd) {
+ return nativeGetHidrawName(fd);
+ }
}
private static native int nativeGetHidrawDescSize(int fd);
@@ -611,4 +648,6 @@
private static native String nativeGetHidrawUniq(int fd);
private static native int nativeGetHidrawBusType(int fd);
+
+ private static native String nativeGetHidrawName(int fd);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 0d5fd14..20bec59 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -587,7 +587,7 @@
@Override
public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
- if (Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
getMagnificationConnectionManager()
.onFullscreenMagnificationActivationChanged(displayId, activated);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 993a1d5..558e07f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -53,8 +53,10 @@
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_RETRIGGER;
import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT;
import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER;
@@ -543,6 +545,8 @@
synchronized (mLock) {
int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0);
FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class);
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_RETRIGGER);
mAssistReceiver.processDelayedFillLocked(requestId, response);
}
}
@@ -1268,7 +1272,13 @@
if(mPreviouslyFillDialogPotentiallyStarted) {
mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER);
} else {
- mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER);
+ if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_EXPLICITLY_REQUESTED);
+ } else {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_NORMAL_TRIGGER);
+ }
}
if (existingResponse != null) {
setViewStatesLocked(
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 5e52e06..82e9a26 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -111,6 +111,11 @@
Slog.i(TAG, "applyRestoredPayload() userId=[" + userId + "], payload size=["
+ payload.length + "].");
+ if (payload.length == 0) {
+ Slog.i(TAG, "CDM backup payload was empty.");
+ return;
+ }
+
ByteBuffer buffer = ByteBuffer.wrap(payload);
// Make sure that payload version matches current version to ensure proper deserialization
@@ -120,15 +125,26 @@
return;
}
- // Read the bytes containing backed-up associations
- byte[] associationsPayload = new byte[buffer.getInt()];
- buffer.get(associationsPayload);
+ // Pre-load the bytes into memory before processing them to ensure payload mal-formatting
+ // error is caught early on.
+ final byte[] associationsPayload;
+ final byte[] requestsPayload;
+ try {
+ // Read the bytes containing backed-up associations
+ associationsPayload = new byte[buffer.getInt()];
+ buffer.get(associationsPayload);
+
+ // Read the bytes containing backed-up system data transfer requests user consent
+ requestsPayload = new byte[buffer.getInt()];
+ buffer.get(requestsPayload);
+ } catch (Exception bufferException) {
+ Slog.e(TAG, "CDM backup payload was mal-formatted.", bufferException);
+ return;
+ }
+
final Associations restoredAssociations = readAssociationsFromPayload(
associationsPayload, userId);
- // Read the bytes containing backed-up system data transfer requests user consent
- byte[] requestsPayload = new byte[buffer.getInt()];
- buffer.get(requestsPayload);
List<SystemDataTransferRequest> restoredRequestsForUser =
mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index edf9fd1..8ac1eb9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -23,7 +23,6 @@
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
-import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -53,7 +52,6 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.IAssociationRequestCallback;
@@ -76,7 +74,6 @@
import android.os.Environment;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.ParcelUuid;
import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -85,7 +82,6 @@
import android.os.UserManager;
import android.util.ArraySet;
import android.util.ExceptionUtils;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.app.IAppOpsService;
@@ -118,16 +114,13 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService {
- static final String TAG = "CDM_CompanionDeviceManagerService";
- static final boolean DEBUG = false;
+ private static final String TAG = "CDM_CompanionDeviceManagerService";
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
@@ -140,7 +133,6 @@
private final IAppOpsService mAppOpsManager;
private final PowerExemptionManager mPowerExemptionManager;
private final PackageManagerInternal mPackageManagerInternal;
- private final PowerManagerInternal mPowerManagerInternal;
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@@ -165,7 +157,8 @@
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
final UserManager userManager = context.getSystemService(UserManager.class);
- mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ final PowerManagerInternal powerManagerInternal = LocalServices.getService(
+ PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
@@ -183,7 +176,7 @@
mDevicePresenceProcessor = new DevicePresenceProcessor(context,
mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
- mPowerManagerInternal);
+ powerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
@@ -250,47 +243,13 @@
@Override
public void onUserUnlocked(@NonNull TargetUser user) {
+ Slog.i(TAG, "onUserUnlocked() user=" + user);
// Notify and bind the app after the phone is unlocked.
- final int userId = user.getUserIdentifier();
- final Set<BluetoothDevice> blueToothDevices =
- mDevicePresenceProcessor.getPendingConnectedDevices().get(userId);
-
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForUser(userId);
-
- if (blueToothDevices != null) {
- for (BluetoothDevice bluetoothDevice : blueToothDevices) {
- final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids();
-
- final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
- ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
-
- for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByAddress(
- bluetoothDevice.getAddress())) {
- Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
- mDevicePresenceProcessor.onBluetoothCompanionDeviceConnected(ai.getId());
- }
-
- for (ObservableUuid observableUuid : observableUuids) {
- if (deviceUuids.contains(observableUuid.getUuid())) {
- Slog.i(TAG, "onUserUnlocked, UUID( "
- + observableUuid.getUuid() + " ) is connected");
- mDevicePresenceProcessor.onDevicePresenceEventByUuid(
- observableUuid, EVENT_BT_CONNECTED);
- }
- }
- }
- }
+ mDevicePresenceProcessor.sendDevicePresenceEventOnUnlocked(user.getUserIdentifier());
}
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
- if (DEBUG) {
- Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/"
- + packageName);
- }
-
// Clear all associations for the package.
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsByPackage(userId, packageName);
@@ -313,8 +272,6 @@
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
- if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName);
-
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsByPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a789384..daa8fdb 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -80,25 +80,6 @@
final int associationId;
try {
- if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
- associationId = getNextIntArgRequired();
- int event = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
- return 0;
- }
-
- if ("simulate-device-uuid-event".equals(cmd) && Flags.devicePresence()) {
- String uuid = getNextArgRequired();
- String packageName = getNextArgRequired();
- int userId = getNextIntArgRequired();
- int event = getNextIntArgRequired();
- ObservableUuid observableUuid = new ObservableUuid(
- userId, ParcelUuid.fromString(uuid), packageName,
- System.currentTimeMillis());
- mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
- return 0;
- }
-
switch (cmd) {
case "list": {
final int userId = getNextIntArgRequired();
@@ -167,6 +148,51 @@
mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
break;
+ case "simulate-device-event": {
+ if (Flags.devicePresence()) {
+ associationId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
+ }
+ break;
+ }
+
+ case "simulate-device-uuid-event": {
+ if (Flags.devicePresence()) {
+ String uuid = getNextArgRequired();
+ String packageName = getNextArgRequired();
+ int userId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ ObservableUuid observableUuid = new ObservableUuid(
+ userId, ParcelUuid.fromString(uuid), packageName,
+ System.currentTimeMillis());
+ mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
+ }
+ break;
+ }
+
+ case "simulate-device-event-device-locked": {
+ if (Flags.devicePresence()) {
+ associationId = getNextIntArgRequired();
+ int userId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ String uuid = getNextArgRequired();
+ ParcelUuid parcelUuid =
+ uuid.equals("null") ? null : ParcelUuid.fromString(uuid);
+ mDevicePresenceProcessor.simulateDeviceEventOnDeviceLocked(
+ associationId, userId, event, parcelUuid);
+ }
+ break;
+ }
+
+ case "simulate-device-event-device-unlocked": {
+ if (Flags.devicePresence()) {
+ int userId = getNextIntArgRequired();
+ mDevicePresenceProcessor.simulateDeviceEventOnUserUnlocked(userId);
+ }
+ break;
+ }
+
case "get-backup-payload": {
final int userId = getNextIntArgRequired();
byte[] payload = mBackupRestoreProcessor.getBackupPayload(userId);
@@ -478,6 +504,17 @@
pw.println(" Make CDM act as if the given DEVICE is BT disconnected base"
+ "on the UUID");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-event-device-locked"
+ + " ASSOCIATION_ID USER_ID DEVICE_EVENT PARCEL_UUID");
+ pw.println(" Simulate device event when the device is locked");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-event-device-unlocked USER_ID");
+ pw.println(" Simulate device unlocked for given user. This will send corresponding");
+ pw.println(" callback after simulate-device-event-device-locked");
+ pw.println(" command has been called.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
}
pw.println(" remove-inactive-associations");
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 9c37881..407b9da 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -73,9 +73,9 @@
private static final String TAG = "CDM_BleCompanionDeviceScanner";
interface Callback {
- void onBleCompanionDeviceFound(int associationId);
+ void onBleCompanionDeviceFound(int associationId, int userId);
- void onBleCompanionDeviceLost(int associationId);
+ void onBleCompanionDeviceLost(int associationId, int userId);
}
private final @NonNull AssociationStore mAssociationStore;
@@ -259,7 +259,7 @@
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
for (AssociationInfo association : associations) {
- mCallback.onBleCompanionDeviceFound(association.getId());
+ mCallback.onBleCompanionDeviceFound(association.getId(), association.getUserId());
}
}
@@ -272,7 +272,7 @@
if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
for (AssociationInfo association : associations) {
- mCallback.onBleCompanionDeviceLost(association.getId());
+ mCallback.onBleCompanionDeviceLost(association.getId(), association.getUserId());
}
}
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 2d345c4..e1a8db4 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -34,20 +34,15 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.companion.association.AssociationStore;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
@SuppressLint("LongLogTag")
public class BluetoothCompanionDeviceConnectionListener
@@ -56,14 +51,13 @@
private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener";
interface Callback {
- void onBluetoothCompanionDeviceConnected(int associationId);
+ void onBluetoothCompanionDeviceConnected(int associationId, int userId);
- void onBluetoothCompanionDeviceDisconnected(int associationId);
+ void onBluetoothCompanionDeviceDisconnected(int associationId, int userId);
void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
- private final UserManager mUserManager;
private final @NonNull AssociationStore mAssociationStore;
private final @NonNull Callback mCallback;
/** A set of ALL connected BT device (not only companion.) */
@@ -71,21 +65,12 @@
private final @NonNull ObservableUuidStore mObservableUuidStore;
- /**
- * A structure hold the connected BT devices that are pending to be reported to the companion
- * app when the user unlocks the local device per userId.
- */
- @GuardedBy("mPendingConnectedDevices")
- @NonNull
- final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
-
BluetoothCompanionDeviceConnectionListener(UserManager userManager,
@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mCallback = callback;
- mUserManager = userManager;
}
public void init(@NonNull BluetoothAdapter btAdapter) {
@@ -111,19 +96,8 @@
if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
return;
}
- // Try to bind and notify the app after the phone is unlocked.
- if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
- Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify "
- + "the application when the phone is unlocked");
- synchronized (mPendingConnectedDevices) {
- Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(
- userId, new HashSet<>());
- bluetoothDevices.add(device);
- mPendingConnectedDevices.put(userId, bluetoothDevices);
- }
- } else {
- onDeviceConnectivityChanged(device, true);
- }
+
+ onDeviceConnectivityChanged(device, true);
}
/**
@@ -149,19 +123,6 @@
return;
}
- // Do not need to report the connectivity since the user is not unlock the phone so
- // that cdm is not bind with the app yet.
- if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- synchronized (mPendingConnectedDevices) {
- Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(userId);
- if (bluetoothDevices != null) {
- bluetoothDevices.remove(device);
- }
- }
-
- return;
- }
-
onDeviceConnectivityChanged(device, false);
}
@@ -190,16 +151,15 @@
if (!association.isNotifyOnDeviceNearby()) continue;
final int id = association.getId();
if (connected) {
- mCallback.onBluetoothCompanionDeviceConnected(id);
+ mCallback.onBluetoothCompanionDeviceConnected(id, association.getUserId());
} else {
- mCallback.onBluetoothCompanionDeviceDisconnected(id);
+ mCallback.onBluetoothCompanionDeviceDisconnected(id, association.getUserId());
}
}
for (ObservableUuid uuid : observableUuids) {
if (deviceUuids.contains(uuid.getUuid())) {
- mCallback.onDevicePresenceEventByUuid(
- uuid, connected ? EVENT_BT_CONNECTED
+ mCallback.onDevicePresenceEventByUuid(uuid, connected ? EVENT_BT_CONNECTED
: EVENT_BT_DISCONNECTED);
}
}
@@ -210,7 +170,8 @@
if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
- mCallback.onBluetoothCompanionDeviceConnected(association.getId());
+ mCallback.onBluetoothCompanionDeviceConnected(
+ association.getId(), association.getUserId());
}
}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
index 642460e..092886c 100644
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
@@ -35,7 +35,6 @@
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
import android.companion.DevicePresenceEvent;
@@ -59,8 +58,10 @@
import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -97,6 +98,8 @@
private final BleCompanionDeviceScanner mBleScanner;
@NonNull
private final PowerManagerInternal mPowerManagerInternal;
+ @NonNull
+ private final UserManager mUserManager;
// NOTE: Same association may appear in more than one of the following sets at the same time.
// (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -129,6 +132,14 @@
private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
new BleDeviceDisappearedScheduler();
+ /**
+ * A structure hold the DevicePresenceEvents that are pending to be reported to the companion
+ * app when the user unlocks the local device per userId.
+ */
+ @GuardedBy("mPendingDevicePresenceEvents")
+ public final SparseArray<List<DevicePresenceEvent>> mPendingDevicePresenceEvents =
+ new SparseArray<>();
+
public DevicePresenceProcessor(@NonNull Context context,
@NonNull CompanionAppBinder companionAppBinder,
UserManager userManager,
@@ -139,6 +150,7 @@
mCompanionAppBinder = companionAppBinder;
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
+ mUserManager = userManager;
mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
associationStore, mObservableUuidStore,
/* BluetoothCompanionDeviceConnectionListener.Callback */ this);
@@ -461,7 +473,14 @@
}
@Override
- public void onBluetoothCompanionDeviceConnected(int associationId) {
+ public void onBluetoothCompanionDeviceConnected(int associationId, int userId) {
+ Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ + "associationId( " + associationId + " )");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ onDeviceLocked(associationId, userId, EVENT_BT_CONNECTED, /* ParcelUuid */ null);
+ return;
+ }
+
synchronized (mBtDisconnectedDevices) {
// A device is considered reconnected within 10 seconds if a pending BLE lost report is
// followed by a detected Bluetooth connection.
@@ -484,9 +503,15 @@
}
@Override
- public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ public void onBluetoothCompanionDeviceDisconnected(int associationId, int userId) {
Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
+ "associationId( " + associationId + " )");
+
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ onDeviceLocked(associationId, userId, EVENT_BT_DISCONNECTED, /* ParcelUuid */ null);
+ return;
+ }
+
// Start BLE scanning when the device is disconnected.
mBleScanner.startScan();
@@ -503,7 +528,13 @@
@Override
- public void onBleCompanionDeviceFound(int associationId) {
+ public void onBleCompanionDeviceFound(int associationId, int userId) {
+ Slog.i(TAG, "onBleCompanionDeviceFound " + "associationId( " + associationId + " )");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ onDeviceLocked(associationId, userId, EVENT_BLE_APPEARED, /* ParcelUuid */ null);
+ return;
+ }
+
onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
synchronized (mBtDisconnectedDevices) {
final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
@@ -514,7 +545,13 @@
}
@Override
- public void onBleCompanionDeviceLost(int associationId) {
+ public void onBleCompanionDeviceLost(int associationId, int userId) {
+ Slog.i(TAG, "onBleCompanionDeviceLost " + "associationId( " + associationId + " )");
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ onDeviceLocked(associationId, userId, EVENT_BLE_APPEARED, /* ParcelUuid */ null);
+ return;
+ }
+
onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
}
@@ -529,18 +566,20 @@
// Make sure the association exists.
enforceAssociationExists(associationId);
+ final AssociationInfo associationInfo = mAssociationStore.getAssociationById(associationId);
+
switch (event) {
case EVENT_BLE_APPEARED:
simulateDeviceAppeared(associationId, event);
break;
case EVENT_BT_CONNECTED:
- onBluetoothCompanionDeviceConnected(associationId);
+ onBluetoothCompanionDeviceConnected(associationId, associationInfo.getUserId());
break;
case EVENT_BLE_DISAPPEARED:
simulateDeviceDisappeared(associationId, event);
break;
case EVENT_BT_DISCONNECTED:
- onBluetoothCompanionDeviceDisconnected(associationId);
+ onBluetoothCompanionDeviceDisconnected(associationId, associationInfo.getUserId());
break;
default:
throw new IllegalArgumentException("Event: " + event + "is not supported");
@@ -558,6 +597,29 @@
onDevicePresenceEventByUuid(uuid, event);
}
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventOnDeviceLocked(
+ int associationId, int userId, int event, ParcelUuid uuid) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-event-device-locked' Shell command,
+ // so the only uid-s allowed to make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDeviceLocked(associationId, userId, event, uuid);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventOnUserUnlocked(int userId) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-event-device-unlocked' Shell command,
+ // so the only uid-s allowed to make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ sendDevicePresenceEventOnUnlocked(userId);
+ }
+
private void simulateDeviceAppeared(int associationId, int state) {
onDevicePresenceEvent(mSimulated, associationId, state);
mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
@@ -660,8 +722,13 @@
+ "]...");
final ParcelUuid parcelUuid = uuid.getUuid();
- final String packageName = uuid.getPackageName();
final int userId = uuid.getUserId();
+ if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+ onDeviceLocked(/* associationId */ -1, userId, eventType, parcelUuid);
+ return;
+ }
+
+ final String packageName = uuid.getPackageName();
final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType,
parcelUuid);
@@ -882,14 +949,6 @@
// what's needed.
}
- /**
- * Return a set of devices that pending to report connectivity
- */
- public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingConnectedDevices) {
- return mBtConnectionListener.mPendingConnectedDevices;
- }
- }
private static void enforceCallerShellOrRoot() {
final int callingUid = Binder.getCallingUid();
@@ -919,6 +978,114 @@
}
/**
+ * Store the positive DevicePresenceEvent in the cache if the current device is still
+ * locked.
+ * Remove the current DevicePresenceEvent if there's a negative event occurs.
+ */
+ private void onDeviceLocked(int associationId, int userId, int event, ParcelUuid uuid) {
+ switch (event) {
+ case EVENT_BLE_APPEARED, EVENT_BT_CONNECTED -> {
+ // Try to bind and notify the app after the phone is unlocked.
+ Slog.i(TAG, "Current user is not in unlocking or unlocked stage yet. "
+ + "Notify the application when the phone is unlocked");
+ synchronized (mPendingDevicePresenceEvents) {
+ final DevicePresenceEvent devicePresenceEvent = new DevicePresenceEvent(
+ associationId, event, uuid);
+ List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents.get(
+ userId, new ArrayList<>());
+ deviceEvents.add(devicePresenceEvent);
+ mPendingDevicePresenceEvents.put(userId, deviceEvents);
+ }
+ }
+ case EVENT_BLE_DISAPPEARED -> {
+ synchronized (mPendingDevicePresenceEvents) {
+ List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents
+ .get(userId);
+ if (deviceEvents != null) {
+ deviceEvents.removeIf(deviceEvent ->
+ deviceEvent.getEvent() == EVENT_BLE_APPEARED
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
+ }
+ }
+ }
+ case EVENT_BT_DISCONNECTED -> {
+ // Do not need to report the event since the user is not unlock the
+ // phone so that cdm is not bind with the app yet.
+ synchronized (mPendingDevicePresenceEvents) {
+ List<DevicePresenceEvent> deviceEvents = mPendingDevicePresenceEvents
+ .get(userId);
+ if (deviceEvents != null) {
+ deviceEvents.removeIf(deviceEvent ->
+ deviceEvent.getEvent() == EVENT_BT_CONNECTED
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
+ }
+ }
+ }
+ default -> Slog.e(TAG, "Event: " + event + "is not supported");
+ }
+ }
+
+ /**
+ * Send the device presence event by userID when the device is unlocked.
+ */
+ public void sendDevicePresenceEventOnUnlocked(int userId) {
+ final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId(
+ userId);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+ // Notify and bind the app after the phone is unlocked.
+ for (DevicePresenceEvent deviceEvent : deviceEvents) {
+ boolean isUuid = deviceEvent.getUuid() != null;
+ if (isUuid) {
+ for (ObservableUuid uuid : observableUuids) {
+ if (uuid.getUuid().equals(deviceEvent.getUuid())) {
+ onDevicePresenceEventByUuid(uuid, EVENT_BT_CONNECTED);
+ }
+ }
+ } else {
+ int event = deviceEvent.getEvent();
+ int associationId = deviceEvent.getAssociationId();
+ final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
+ associationId);
+
+ if (associationInfo == null) {
+ return;
+ }
+
+ switch(event) {
+ case EVENT_BLE_APPEARED:
+ onBleCompanionDeviceFound(
+ associationInfo.getId(), associationInfo.getUserId());
+ break;
+ case EVENT_BT_CONNECTED:
+ onBluetoothCompanionDeviceConnected(
+ associationInfo.getId(), associationInfo.getUserId());
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+ }
+
+ clearPendingDevicePresenceEventsByUserId(userId);
+ }
+
+ private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) {
+ synchronized (mPendingDevicePresenceEvents) {
+ return mPendingDevicePresenceEvents.get(userId, new ArrayList<>());
+ }
+ }
+
+ private void clearPendingDevicePresenceEventsByUserId(int userId) {
+ synchronized (mPendingDevicePresenceEvents) {
+ mPendingDevicePresenceEvents.get(userId).clear();
+ }
+ }
+
+ /**
* Dumps system information about devices that are marked as "present".
*/
public void dump(@NonNull PrintWriter out) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 3ec6e47..8b98d12 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -272,7 +272,8 @@
params,
DisplayManagerGlobal.getInstance(),
isVirtualCameraEnabled()
- ? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
+ ? new VirtualCameraController(
+ params.getDevicePolicy(POLICY_TYPE_CAMERA), deviceId)
: null);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 3bb1e33..4547bd6 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -58,19 +58,21 @@
@Nullable private IVirtualCameraService mVirtualCameraService;
@DevicePolicy
private final int mCameraPolicy;
+ private final int mDeviceId;
@GuardedBy("mCameras")
private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
- public VirtualCameraController(@DevicePolicy int cameraPolicy) {
- this(/* virtualCameraService= */ null, cameraPolicy);
+ public VirtualCameraController(@DevicePolicy int cameraPolicy, int deviceId) {
+ this(/* virtualCameraService= */ null, cameraPolicy, deviceId);
}
@VisibleForTesting
VirtualCameraController(IVirtualCameraService virtualCameraService,
- @DevicePolicy int cameraPolicy) {
+ @DevicePolicy int cameraPolicy, int deviceId) {
mVirtualCameraService = virtualCameraService;
mCameraPolicy = cameraPolicy;
+ mDeviceId = deviceId;
}
/**
@@ -251,7 +253,7 @@
VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config);
synchronized (mServiceLock) {
return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
- serviceConfiguration);
+ serviceConfiguration, mDeviceId);
}
}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index b28bc1f..16a9933 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.app.ActivityOptions;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.contextualsearch.CallbackToken;
import android.app.contextualsearch.ContextualSearchManager;
import android.app.contextualsearch.ContextualSearchState;
import android.app.contextualsearch.IContextualSearchCallback;
@@ -164,7 +165,7 @@
}
}
- private Intent getContextualSearchIntent(int entrypoint, IBinder mToken) {
+ private Intent getContextualSearchIntent(int entrypoint, CallbackToken mToken) {
final Intent launchIntent = getResolvedLaunchIntent();
if (launchIntent == null) {
return null;
@@ -256,14 +257,14 @@
}
private class ContextualSearchManagerStub extends IContextualSearchManager.Stub {
- private @Nullable IBinder mToken;
+ private @Nullable CallbackToken mToken;
@Override
public void startContextualSearch(int entrypoint) {
synchronized (this) {
if (DEBUG_USER) Log.d(TAG, "startContextualSearch");
enforcePermission("startContextualSearch");
- mToken = new Binder();
+ mToken = new CallbackToken();
// We get the launch intent with the system server's identity because the system
// server has READ_FRAME_BUFFER permission to get the screenshot and because only
// the system server can invoke non-exported activities.
@@ -284,7 +285,7 @@
if (DEBUG_USER) {
Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback);
}
- if (mToken == null || !mToken.equals(token)) {
+ if (mToken == null || !mToken.getToken().equals(token)) {
if (DEBUG_USER) {
Log.e(TAG, "getContextualSearchState: invalid token, returning error");
}
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index e3f16ae..ac19d8b 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -20,6 +20,11 @@
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -86,9 +91,11 @@
private static class MediaProjectionSession {
final int mUid;
final long mSessionId;
+ final boolean mIsExempted;
- MediaProjectionSession(int uid, long sessionId) {
+ MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
mUid = uid;
+ mIsExempted = isExempted;
mSessionId = sessionId;
}
}
@@ -105,11 +112,28 @@
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mMediaProjectionSession.mSessionId,
+ mMediaProjectionSession.mUid,
+ mMediaProjectionSession.mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mMediaProjectionSession.mSessionId,
+ mMediaProjectionSession.mUid,
+ mMediaProjectionSession.mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStop");
try {
@@ -137,20 +161,20 @@
}
if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
-
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
init(getContext().getSystemService(MediaProjectionManager.class),
LocalServices.getService(WindowManagerInternal.class),
- getExemptedPackages());
+ LocalServices.getService(PackageManagerInternal.class),
+ getExemptedPackages()
+ );
if (sensitiveContentAppProtection()) {
publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE,
- new SensitiveContentProtectionManagerServiceBinder(mPackageManagerInternal));
+ new SensitiveContentProtectionManagerServiceBinder());
}
}
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
- ArraySet<String> exemptedPackages) {
+ PackageManagerInternal packageManagerInternal, ArraySet<String> exemptedPackages) {
if (DEBUG) Log.d(TAG, "init");
Objects.requireNonNull(projectionManager);
@@ -158,6 +182,7 @@
mProjectionManager = projectionManager;
mWindowManager = windowManager;
+ mPackageManagerInternal = packageManagerInternal;
mExemptedPackages = exemptedPackages;
// TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
@@ -207,28 +232,27 @@
}
private void onProjectionStart(MediaProjectionInfo projectionInfo) {
- // exempt on device screen recorder as well.
- if ((mExemptedPackages != null && mExemptedPackages.contains(
+ boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains(
projectionInfo.getPackageName()))
- || canRecordSensitiveContent(projectionInfo.getPackageName())) {
- Log.w(TAG, projectionInfo.getPackageName() + " is exempted.");
- return;
- }
+ || canRecordSensitiveContent(projectionInfo.getPackageName())
+ || isAutofillServiceRecorderPackage(projectionInfo.getUserHandle().getIdentifier(),
+ projectionInfo.getPackageName());
// TODO(b/324447419): move GlobalSettings lookup to background thread
- boolean disableScreenShareProtections =
- Settings.Global.getInt(getContext().getContentResolver(),
- DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
- if (disableScreenShareProtections) {
- Log.w(TAG, "Screen share protections disabled, ignoring projection start");
+ boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+ int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
+ projectionInfo.getUserHandle().getIdentifier());
+ mMediaProjectionSession = new MediaProjectionSession(
+ uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+
+ if (isPackageExempted || isFeatureDisabled) {
+ Log.w(TAG, "projection session is exempted, package ="
+ + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
return;
}
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = true;
- int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
- projectionInfo.getUserHandle().getIdentifier());
- // TODO review sessionId, whether to use a sequence generator or random is good?
- mMediaProjectionSession = new MediaProjectionSession(uid, new Random().nextLong());
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
}
@@ -274,8 +298,9 @@
// notify windowmanager of any currently posted sensitive content notifications
ArraySet<PackageInfo> packageInfos =
getSensitivePackagesFromNotifications(notifications, rankingMap);
-
- mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+ if (packageInfos.size() > 0) {
+ mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+ }
}
private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
@@ -401,6 +426,7 @@
if (!mProjectionActive) {
return;
}
+
if (DEBUG) {
Log.d(TAG, "setSensitiveContentProtection - current package=" + packageInfo
+ ", isShowingSensitiveContent=" + isShowingSensitiveContent
@@ -431,15 +457,29 @@
}
}
- private final class SensitiveContentProtectionManagerServiceBinder
- extends ISensitiveContentProtectionManager.Stub {
- private final PackageManagerInternal mPackageManagerInternal;
-
- SensitiveContentProtectionManagerServiceBinder(
- PackageManagerInternal packageManagerInternal) {
- mPackageManagerInternal = packageManagerInternal;
+ // TODO: b/328251279 - Autofill service exemption is temporary and will be removed in future.
+ private boolean isAutofillServiceRecorderPackage(int userId, String projectionPackage) {
+ String autofillServiceName = Settings.Secure.getStringForUser(
+ getContext().getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, userId);
+ if (DEBUG) {
+ Log.d(TAG, "autofill service for user " + userId + " is " + autofillServiceName);
}
+ if (autofillServiceName == null) {
+ return false;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(autofillServiceName);
+ if (serviceComponent == null) {
+ return false;
+ }
+ String autofillServicePackage = serviceComponent.getPackageName();
+
+ return autofillServicePackage != null
+ && autofillServicePackage.equals(projectionPackage);
+ }
+
+ private final class SensitiveContentProtectionManagerServiceBinder
+ extends ISensitiveContentProtectionManager.Stub {
public void setSensitiveContentProtection(IBinder windowToken, String packageName,
boolean isShowingSensitiveContent) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index c18bacb..72a55dbe 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -177,6 +177,7 @@
"android.hardware.biometrics.fingerprint.IFingerprint/",
"android.hardware.bluetooth.IBluetoothHci/",
"android.hardware.camera.provider.ICameraProvider/",
+ "android.hardware.drm.IDrmFactory/",
"android.hardware.gnss.IGnss/",
"android.hardware.graphics.allocator.IAllocator/",
"android.hardware.graphics.composer3.IComposer/",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 04dd2f3..90a9d1b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2659,6 +2659,9 @@
}
updateNumForegroundServicesLocked();
}
+
+ maybeUpdateShortFgsTrackingLocked(r,
+ extendShortServiceTimeout);
// Even if the service is already a FGS, we need to update the notification,
// so we need to call it again.
signalForegroundServiceObserversLocked(r);
@@ -2670,8 +2673,6 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- maybeUpdateShortFgsTrackingLocked(r,
- extendShortServiceTimeout);
maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
@@ -4158,7 +4159,7 @@
|| (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
&& c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)),
b.client);
- if (!s.mOomAdjBumpedInExec && (serviceBindingOomAdjPolicy
+ if (!s.wasOomAdjUpdated() && (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
needOomAdj = true;
mAm.enqueueOomAdjTargetLocked(s.app);
@@ -4308,7 +4309,7 @@
}
serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
@@ -4456,7 +4457,7 @@
}
serviceDoneExecutingLocked(r, inDestroying, false, false,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
@@ -5004,13 +5005,16 @@
}
}
}
- if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
+ if (r.app != null
&& r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
- // Force an immediate oomAdjUpdate, so the client app could be in the correct process
- // state before doing any service related transactions
+ // Enqueue the oom adj target anyway for opportunistic oom adj updates.
mAm.enqueueOomAdjTargetLocked(r.app);
- mAm.updateOomAdjPendingTargetsLocked(oomAdjReason);
- r.mOomAdjBumpedInExec = true;
+ r.updateOomAdjSeq();
+ if (oomAdjReason != OOM_ADJ_REASON_NONE) {
+ // Force an immediate oomAdjUpdate, so the client app could be in the correct
+ // process state before doing any service related transactions
+ mAm.updateOomAdjPendingTargetsLocked(oomAdjReason);
+ }
}
r.executeFg |= fg;
r.executeNesting++;
@@ -5050,7 +5054,7 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
throw e;
} catch (RemoteException e) {
@@ -5058,7 +5062,7 @@
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
return false;
}
@@ -5854,8 +5858,8 @@
// Force an immediate oomAdjUpdate, so the host app could be in the correct
// process state before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
+ r.updateOomAdjSeq();
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
- r.mOomAdjBumpedInExec = true;
} else {
// Since we skipped the oom adj update, the Service#onCreate() might be running in
// the cached state, if the service process drops into the cached state after the call.
@@ -5896,7 +5900,7 @@
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_STOP_SERVICE : OOM_ADJ_REASON_NONE);
// Cleanup.
@@ -5932,7 +5936,7 @@
null, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN));
}
- sendServiceArgsLocked(r, execInFg, r.mOomAdjBumpedInExec);
+ sendServiceArgsLocked(r, execInFg, r.wasOomAdjUpdated());
if (r.delayed) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
@@ -6119,7 +6123,7 @@
}
}
- boolean oomAdjusted = Flags.serviceBindingOomAdjPolicy() && r.mOomAdjBumpedInExec;
+ boolean oomAdjusted = Flags.serviceBindingOomAdjPolicy() && r.wasOomAdjUpdated();
// Tell the service that it has been unbound.
if (r.app != null && r.app.isThreadReady()) {
@@ -6132,7 +6136,7 @@
bumpServiceExecutingLocked(r, false, "bring down unbind",
oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
oomAdjusted /* skipTimeoutIfPossible */);
- oomAdjusted |= r.mOomAdjBumpedInExec;
+ oomAdjusted |= r.wasOomAdjUpdated();
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -6292,7 +6296,7 @@
oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
oomAdjusted /* skipTimeoutIfPossible */);
mDestroyingServices.add(r);
- oomAdjusted |= r.mOomAdjBumpedInExec;
+ oomAdjusted |= r.wasOomAdjUpdated();
r.destroying = true;
r.app.getThread().scheduleStopService(r);
} catch (Exception e) {
@@ -6579,7 +6583,7 @@
}
final long origId = mAm.mInjector.clearCallingIdentity();
serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
- !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec || needOomAdj
+ !Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated() || needOomAdj
? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
mAm.mInjector.restoreCallingIdentity(origId);
} else {
@@ -6645,7 +6649,7 @@
} else {
// Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
}
- r.mOomAdjBumpedInExec = false;
+ r.updateOomAdjSeq();
}
r.executeFg = false;
if (r.tracker != null) {
@@ -7029,7 +7033,6 @@
sr.setProcess(null, null, 0, null);
sr.isolationHostProc = null;
sr.executeNesting = 0;
- sr.mOomAdjBumpedInExec = false;
synchronized (mAm.mProcessStats.mLock) {
sr.forceClearTracker();
}
@@ -9013,6 +9016,7 @@
r.isForeground = true;
r.mFgsEnterTime = SystemClock.uptimeMillis();
r.foregroundServiceType = options.mForegroundServiceTypes;
+ r.updateOomAdjSeq();
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
@@ -9075,6 +9079,7 @@
}
}
if (r != null) {
+ r.updateOomAdjSeq();
bringDownServiceLocked(r, false);
} else {
Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
@@ -9100,6 +9105,7 @@
}
}
if (r != null) {
+ r.updateOomAdjSeq();
bringDownServiceLocked(r, false);
} else {
Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4f1a35c..47f03f3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -414,6 +414,7 @@
import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.BinderInternal.BinderProxyCountEventListener;
import com.android.internal.os.BinderTransactionNameResolver;
import com.android.internal.os.ByteTransferPipe;
import com.android.internal.os.IResultReceiver;
@@ -614,8 +615,8 @@
private static final int MINIMUM_MEMORY_GROWTH_THRESHOLD = 10 * 1000; // 10 MB
/**
- * The number of binder proxies we need to have before we start warning and
- * dumping debug info.
+ * The number of binder proxies we need to have before we start dumping debug info
+ * and kill the offenders.
*/
private static final int BINDER_PROXY_HIGH_WATERMARK = 6000;
@@ -625,6 +626,11 @@
*/
private static final int BINDER_PROXY_LOW_WATERMARK = 5500;
+ /**
+ * The number of binder proxies we need to have before we start warning.
+ */
+ private static final int BINDER_PROXY_WARNING_WATERMARK = 5750;
+
// Max character limit for a notification title. If the notification title is larger than this
// the notification will not be legible to the user.
private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
@@ -1680,6 +1686,11 @@
PermissionManagerServiceInternal mPermissionManagerInt;
private TestUtilityService mTestUtilityService;
+ // Packages which have received a (LOCKED_)BOOT_COMPLETED broadcast since
+ // the private space profile has been started
+ @GuardedBy("this")
+ private final ArraySet<String> mPrivateSpaceBootCompletedPackages = new ArraySet<String>();
+
/**
* Whether to force background check on all apps (for battery saver) or not.
*/
@@ -2307,6 +2318,19 @@
@Override
public void onUserStopped(@NonNull TargetUser user) {
mService.mBatteryStatsService.onCleanupUser(user.getUserIdentifier());
+
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+ final UserManagerInternal umInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umInternal.getUserInfo(user.getUserIdentifier());
+
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ synchronized (mService) {
+ mService.mPrivateSpaceBootCompletedPackages.clear();
+ }
+ }
+ }
}
public ActivityManagerService getService() {
@@ -5042,13 +5066,32 @@
}
/**
- * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+ * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped,
+ * or when the package first starts in private space
*/
private void maybeSendBootCompletedLocked(ProcessRecord app) {
- if (!android.content.pm.Flags.stayStopped()) return;
- // Nothing to do if it wasn't previously stopped
- if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
- return;
+ boolean sendBroadcast = false;
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+ final UserManagerInternal umInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umInternal.getUserInfo(app.userId);
+
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ // Packages in private space get deferred boot completed whenever they start the
+ // first time since profile start
+ if (!mPrivateSpaceBootCompletedPackages.contains(app.info.packageName)) {
+ mPrivateSpaceBootCompletedPackages.add(app.info.packageName);
+ sendBroadcast = true;
+ } // else, stopped packages in private space may still hit the logic below
+ }
+ }
+ if (!sendBroadcast) {
+ if (!android.content.pm.Flags.stayStopped()) return;
+ // Nothing to do if it wasn't previously stopped
+ if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ return;
+ }
}
// Send LOCKED_BOOT_COMPLETED, if necessary
@@ -7978,6 +8021,18 @@
return uidRecord != null && !uidRecord.isSetIdle();
}
+ @Override
+ public long getUidLastIdleElapsedTime(int uid, String callingPackage) {
+ if (!hasUsageStatsPermission(callingPackage)) {
+ enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+ "getUidLastIdleElapsedTime");
+ }
+ synchronized (mProcLock) {
+ final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+ return uidRecord != null ? uidRecord.getRealLastIdleTime() : 0;
+ }
+ }
+
@GuardedBy("mUidFrozenStateChangedCallbackList")
private final RemoteCallbackList<IUidFrozenStateChangedCallback>
mUidFrozenStateChangedCallbackList = new RemoteCallbackList<>();
@@ -9014,34 +9069,10 @@
t.traceBegin("setBinderProxies");
BinderInternal.nSetBinderProxyCountWatermarks(BINDER_PROXY_HIGH_WATERMARK,
- BINDER_PROXY_LOW_WATERMARK);
+ BINDER_PROXY_LOW_WATERMARK, BINDER_PROXY_WARNING_WATERMARK);
BinderInternal.nSetBinderProxyCountEnabled(true);
- BinderInternal.setBinderProxyCountCallback(
- (uid) -> {
- Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
- + Process.myUid());
- BinderProxy.dumpProxyDebugInfo();
- CriticalEventLog.getInstance().logExcessiveBinderCalls(uid);
- if (uid == Process.SYSTEM_UID) {
- Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
- } else {
- killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
- ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
- ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
- "Too many Binders sent to SYSTEM");
- // We need to run a GC here, because killing the processes involved
- // actually isn't guaranteed to free up the proxies; in fact, if the
- // GC doesn't run for a long time, we may even exceed the global
- // proxy limit for a process (20000), resulting in system_server itself
- // being killed.
- // Note that the GC here might not actually clean up all the proxies,
- // because the binder reference decrements will come in asynchronously;
- // but if new processes belonging to the UID keep adding proxies, we
- // will get another callback here, and run the GC again - this time
- // cleaning up the old proxies.
- VMRuntime.getRuntime().requestConcurrentGC();
- }
- }, mHandler);
+ BinderInternal.setBinderProxyCountCallback(new MyBinderProxyCountEventListener(),
+ mHandler);
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
@@ -9056,6 +9087,46 @@
}
}
+ private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener {
+ @Override
+ public void onLimitReached(int uid) {
+ Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+ + Process.myUid());
+ BinderProxy.dumpProxyDebugInfo();
+ CriticalEventLog.getInstance().logExcessiveBinderCalls(uid);
+ if (uid == Process.SYSTEM_UID) {
+ Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+ } else {
+ killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+ ApplicationExitInfo.SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+ "Too many Binders sent to SYSTEM");
+ // We need to run a GC here, because killing the processes involved
+ // actually isn't guaranteed to free up the proxies; in fact, if the
+ // GC doesn't run for a long time, we may even exceed the global
+ // proxy limit for a process (20000), resulting in system_server itself
+ // being killed.
+ // Note that the GC here might not actually clean up all the proxies,
+ // because the binder reference decrements will come in asynchronously;
+ // but if new processes belonging to the UID keep adding proxies, we
+ // will get another callback here, and run the GC again - this time
+ // cleaning up the old proxies.
+ VMRuntime.getRuntime().requestConcurrentGC();
+ }
+ }
+
+ @Override
+ public void onWarningThresholdReached(int uid) {
+ if (Flags.logExcessiveBinderProxies()) {
+ Slog.w(TAG, "Uid " + uid + " sent too many ("
+ + BINDER_PROXY_WARNING_WATERMARK + ") Binders to uid " + Process.myUid());
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.EXCESSIVE_BINDER_PROXY_COUNT_REPORTED,
+ uid);
+ }
+ }
+ }
+
private void watchDeviceProvisioning(Context context) {
// setting system property based on whether device is provisioned
@@ -18189,6 +18260,11 @@
}
@Override
+ public boolean startUserInBackground(final int userId) {
+ return ActivityManagerService.this.startUserInBackground(userId);
+ }
+
+ @Override
public void killForegroundAppsForUser(@UserIdInt int userId) {
final ArrayList<ProcessRecord> procs = new ArrayList<>();
synchronized (mProcLock) {
@@ -20868,4 +20944,14 @@
}
mOomAdjuster.mCachedAppOptimizer.binderError(debugPid, app, code, flags, err);
}
+
+ @GuardedBy("this")
+ void enqueuePendingTopAppIfNecessaryLocked() {
+ mPendingStartActivityUids.enqueuePendingTopAppIfNecessaryLocked(this);
+ }
+
+ @GuardedBy("this")
+ void clearPendingTopAppLocked() {
+ mPendingStartActivityUids.clear();
+ }
}
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 595d16d..b55f35d 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -44,6 +44,7 @@
private static final int LOW_MEM_AND_SWAP_UTIL = 6;
private static final int LOW_FILECACHE_AFTER_THRASHING = 7;
private static final int LOW_MEM = 8;
+ private static final int DIRECT_RECL_STUCK = 9;
/**
* Processes the LMK_KILL_OCCURRED packet data
@@ -98,6 +99,8 @@
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_FILECACHE_AFTER_THRASHING;
case LOW_MEM:
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_MEM;
+ case DIRECT_RECL_STUCK:
+ return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__DIRECT_RECL_STUCK;
default:
return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5e91cd3..5a750c2 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -71,7 +71,7 @@
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
import static android.os.Process.SCHED_OTHER;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
@@ -409,6 +409,12 @@
private final OomAdjusterDebugLogger mLogger;
+ /**
+ * The process state of the current TOP app.
+ */
+ @GuardedBy("mService")
+ protected int mProcessStateCurTop = PROCESS_STATE_TOP;
+
/** Overrideable by a test */
@VisibleForTesting
protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId,
@@ -518,59 +524,6 @@
}
/**
- * Perform oom adj update on the given process. It does NOT do the re-computation
- * if there is a cycle, caller should check {@link #mProcessesInCycle} and do it on its own.
- */
- @GuardedBy({"mService", "mProcLock"})
- private boolean performUpdateOomAdjLSP(ProcessRecord app, int cachedAdj,
- ProcessRecord topApp, long now, @OomAdjReason int oomAdjReason) {
- if (app.getThread() == null) {
- return false;
- }
-
- app.mState.resetCachedInfo();
- app.mState.setCurBoundByNonBgRestrictedApp(false);
- UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
-
- // Check if this process is in the pending list too, remove from pending list if so.
- mPendingProcessSet.remove(app);
-
- mProcessesInCycle.clear();
- computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true);
- if (!mProcessesInCycle.isEmpty()) {
- // We can't use the score here if there is a cycle, abort.
- for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) {
- // Reset the adj seq
- mProcessesInCycle.valueAt(i).mState.setCompletedAdjSeq(mAdjSeq - 1);
- }
- return true;
- }
-
- if (uidRec != null) {
- // After uidRec.reset() above, for UidRecord with multiple processes (ProcessRecord),
- // we need to apply all ProcessRecord into UidRecord.
- uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
- if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
- && (uidRec.getSetProcState() != uidRec.getCurProcState()
- || uidRec.getSetCapability() != uidRec.getCurCapability()
- || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
- final ActiveUids uids = mTmpUidRecords;
- uids.clear();
- uids.put(uidRec.getUid(), uidRec);
- updateUidsLSP(uids, SystemClock.elapsedRealtime());
- }
- }
-
- return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime(), oomAdjReason);
- }
-
- /**
* Update OomAdj for all processes in LRU list
*/
@GuardedBy("mService")
@@ -599,6 +552,7 @@
@GuardedBy({"mService", "mProcLock"})
protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ mProcessStateCurTop = mService.mAtmInternal.getTopProcessState();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
@@ -649,54 +603,14 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
- mAdjSeq++;
final ProcessStateRecord state = app.mState;
- final boolean wasCached = state.isCached();
- final int oldAdj = state.getCurRawAdj();
- final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
- ? oldAdj : UNKNOWN_ADJ;
-
- // Firstly, try to see if the importance of itself gets changed
- final boolean wasBackground = ActivityManager.isProcStateBackground(
- state.getSetProcState());
- final int oldCap = state.getSetCapability();
- state.setContainsCycle(false);
- state.setProcStateChanged(false);
- state.resetCachedInfo();
- state.setCurBoundByNonBgRestrictedApp(false);
- // Check if this process is in the pending list too, remove from pending list if so.
- mPendingProcessSet.remove(app);
- app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
- boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp,
- SystemClock.uptimeMillis(), oomAdjReason);
- // The 'app' here itself might or might not be in the cycle, for example,
- // the case A <=> B vs. A -> B <=> C; anyway, if we spot a cycle here, re-compute them.
- if (!success || (wasCached == state.isCached() && oldAdj != INVALID_ADJ
- && mProcessesInCycle.isEmpty() /* Force re-compute if there is a cycle */
- && oldCap == state.getCurCapability()
- && wasBackground == ActivityManager.isProcStateBackground(
- state.getSetProcState()))) {
- mProcessesInCycle.clear();
- // Okay, it's unchanged, it won't impact any service it binds to, we're done here.
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG_OOM_ADJ, "No oomadj changes for " + app);
- }
- mService.mOomAdjProfiler.oomAdjEnded();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- return success;
- }
// Next to find out all its reachable processes
ArrayList<ProcessRecord> processes = mTmpProcessList;
ActiveUids uids = mTmpUidRecords;
mPendingProcessSet.add(app);
-
- // Add all processes with cycles into the list to scan
- for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) {
- mPendingProcessSet.add(mProcessesInCycle.valueAt(i));
- }
- mProcessesInCycle.clear();
+ mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
boolean containsCycle = collectReachableProcessesLocked(mPendingProcessSet,
processes, uids);
@@ -704,14 +618,8 @@
// Clear the pending set as they should've been included in 'processes'.
mPendingProcessSet.clear();
- if (!containsCycle) {
- // Remove this app from the return list because we've done the computation on it.
- processes.remove(app);
- }
-
int size = processes.size();
if (size > 0) {
- mAdjSeq--;
// Update these reachable processes
updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, containsCycle, false);
} else if (state.getCurRawAdj() == UNKNOWN_ADJ) {
@@ -723,11 +631,25 @@
SystemClock.elapsedRealtime(), oomAdjReason);
}
mTmpProcessList.clear();
+ mService.clearPendingTopAppLocked();
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return true;
}
+ @GuardedBy({"mService", "mProcLock"})
+ protected int enqueuePendingTopAppIfNecessaryLSP() {
+ final int prevTopProcessState = mService.mAtmInternal.getTopProcessState();
+ mService.enqueuePendingTopAppIfNecessaryLocked();
+ final int topProcessState = mService.mAtmInternal.getTopProcessState();
+ if (prevTopProcessState != topProcessState) {
+ // Unlikely but possible: WM just updated the top process state, it may have
+ // enqueued the new top app to the pending top UID list. Enqueue that one here too.
+ mService.enqueuePendingTopAppIfNecessaryLocked();
+ }
+ return topProcessState;
+ }
+
/**
* Collect the reachable processes from the given {@code apps}, the result will be
* returned in the given {@code processes}, which will include the processes from
@@ -930,6 +852,7 @@
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
+ mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
final ArrayList<ProcessRecord> processes = mTmpProcessList;
final ActiveUids uids = mTmpUidRecords;
@@ -939,6 +862,7 @@
updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, true, false);
}
processes.clear();
+ mService.clearPendingTopAppLocked();
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1337,6 +1261,7 @@
// Avoid trimming processes that are still initializing. If they aren't
// hosting any components yet because they may be unfairly killed.
// We however apply the oom scores set at #setAttachingProcessStatesLSP.
+ updateAppUidRecLSP(app);
continue;
}
@@ -1882,7 +1807,7 @@
state.setSystemNoUi(false);
- final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
+ final int PROCESS_STATE_CUR_TOP = mProcessStateCurTop;
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
@@ -2287,7 +2212,7 @@
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
- if (foregroundAudioControl()) { // flag check
+ if (roForegroundAudioControl()) { // flag check
final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
| FOREGROUND_SERVICE_TYPE_CAMERA
| FOREGROUND_SERVICE_TYPE_MICROPHONE
@@ -3286,13 +3211,15 @@
// If the partial values are no better, skip until the next
// attempt
if (client.getCurRawProcState() >= procState
- && client.getCurRawAdj() >= adj) {
+ && client.getCurRawAdj() >= adj
+ && (client.getCurCapability() & app.mState.getCurCapability())
+ == client.getCurCapability()) {
return true;
}
// Else use the client's partial procstate and adj to adjust the
// effect of the binding
} else {
- return true;
+ return false;
}
}
}
@@ -3754,7 +3681,7 @@
for (int i = N - 1; i >= 0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
final long bgTime = uidRec.getLastBackgroundTime();
- final long idleTime = uidRec.getLastIdleTime();
+ final long idleTime = uidRec.getLastIdleTimeIfStillIdle();
if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
if (bgTime <= maxBgTime) {
EventLogTags.writeAmUidIdle(uidRec.getUid());
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 5feac1f..00e1482 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -737,6 +737,7 @@
@Override
protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ mProcessStateCurTop = mService.mAtmInternal.getTopProcessState();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
@@ -763,6 +764,7 @@
@Override
protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
mLastReason = oomAdjReason;
+ mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index da09317..e912d07 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -87,4 +87,22 @@
synchronized boolean isPendingTopUid(int uid) {
return mPendingUids.get(uid) != null;
}
+
+ // Must called with AMS locked.
+ synchronized void enqueuePendingTopAppIfNecessaryLocked(ActivityManagerService ams) {
+ for (int i = 0, size = mPendingUids.size(); i < size; i++) {
+ final Pair<Integer, Long> p = mPendingUids.valueAt(i);
+ final ProcessRecord app;
+ synchronized (ams.mPidsSelfLocked) {
+ app = ams.mPidsSelfLocked.get(p.first);
+ }
+ if (app != null) {
+ ams.enqueueOomAdjTargetLocked(app);
+ }
+ }
+ }
+
+ synchronized void clear() {
+ mPendingUids.clear();
+ }
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index e3aac02..5834dcd 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -267,9 +267,13 @@
int mAllowStart_byBindings = REASON_DENIED;
/**
- * Whether or not we've bumped its oom adj scores during its execution.
+ * The oom adj seq number snapshot of the host process. We're taking a snapshot
+ * before executing the service. Since we may or may not bump the host process's
+ * proc state / oom adj value before that, at the end of the execution, we could
+ * compare this seq against the current seq of the host process to see if we could
+ * skip the oom adj update from there too.
*/
- boolean mOomAdjBumpedInExec;
+ int mAdjSeq;
/**
* Whether to use the new "while-in-use permission" logic for FGS start
@@ -1884,4 +1888,17 @@
}
return true;
}
+
+ /**
+ * @return {@code true} if the host process has updated its oom adj scores.
+ */
+ boolean wasOomAdjUpdated() {
+ return app != null && app.mState.getAdjSeq() > mAdjSeq;
+ }
+
+ void updateOomAdjSeq() {
+ if (app != null) {
+ mAdjSeq = app.mState.getAdjSeq();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 45fd470..86fa0fc 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.Manifest;
+import android.annotation.ElapsedRealtimeLong;
import android.app.ActivityManager;
import android.content.pm.PackageManager;
import android.os.SystemClock;
@@ -65,8 +66,19 @@
@CompositeRWLock({"mService", "mProcLock"})
private long mLastBackgroundTime;
+ /**
+ * Last time the UID became idle. This is set to 0, once the UID becomes active.
+ */
+ @ElapsedRealtimeLong
@CompositeRWLock({"mService", "mProcLock"})
- private long mLastIdleTime;
+ private long mLastIdleTimeIfStillIdle;
+
+ /**
+ * Last time the UID became idle. Unlike {@link #mLastIdleTimeIfStillIdle}, we never clear it.
+ */
+ @ElapsedRealtimeLong
+ @CompositeRWLock({"mService", "mProcLock"})
+ private long mRealLastIdleTime;
@CompositeRWLock({"mService", "mProcLock"})
private boolean mEphemeral;
@@ -257,14 +269,28 @@
mLastBackgroundTime = lastBackgroundTime;
}
+ /**
+ * Last time the UID became idle. This is set to 0, once the UID becomes active.
+ */
@GuardedBy(anyOf = {"mService", "mProcLock"})
- long getLastIdleTime() {
- return mLastIdleTime;
+ long getLastIdleTimeIfStillIdle() {
+ return mLastIdleTimeIfStillIdle;
+ }
+
+ /**
+ * Last time the UID became idle. Unlike {@link #getLastIdleTimeIfStillIdle}, we never clear it.
+ */
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ long getRealLastIdleTime() {
+ return mRealLastIdleTime;
}
@GuardedBy({"mService", "mProcLock"})
- void setLastIdleTime(long lastActiveTime) {
- mLastIdleTime = lastActiveTime;
+ void setLastIdleTime(@ElapsedRealtimeLong long lastIdleTime) {
+ mLastIdleTimeIfStillIdle = lastIdleTime;
+ if (lastIdleTime > 0) {
+ mRealLastIdleTime = lastIdleTime;
+ }
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a917909..60a8b50 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -576,6 +576,8 @@
}
// allowDelayedLocking set here as stopping user is done without any explicit request
// from outside.
+ Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
+ currentlyRunningLru.size(), userId);
if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
@@ -669,6 +671,14 @@
}
private void sendLockedBootCompletedBroadcast(IIntentReceiver receiver, @UserIdInt int userId) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+ final UserInfo userInfo = getUserInfo(userId);
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ Slogf.i(TAG, "Skipping LOCKED_BOOT_COMPLETED for private profile user #" + userId);
+ return;
+ }
+ }
final Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
@@ -875,6 +885,13 @@
mHandler.obtainMessage(USER_UNLOCKED_MSG, userId, 0).sendToTarget();
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
+ if (userInfo.isPrivateProfile()) {
+ Slogf.i(TAG, "Skipping BOOT_COMPLETED for private profile user #" + userId);
+ return;
+ }
+ }
Slogf.i(TAG, "Posting BOOT_COMPLETED user #" + userId);
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
@@ -991,6 +1008,13 @@
if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
+ // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
+ final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+ if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
+ }
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
int[] usersToStop = getUsersToStopLU(userId);
// If one of related users is system or current, no related users should be stopped
@@ -1540,6 +1564,7 @@
}
if (userInfo.isGuest() || userInfo.isEphemeral()) {
// This is a user to be stopped.
+ Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
synchronized (mLock) {
stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
null, null);
@@ -2219,6 +2244,10 @@
mUserSwitchObservers.finishBroadcast();
}
+ /**
+ * Possibly stops the given full user (or its profile) when we switch out of it, if dictated
+ * by policy.
+ */
private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -2228,20 +2257,27 @@
hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
synchronized (mLock) {
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
- boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch();
- if (!disallowRunInBg) {
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users",
- oldUserId);
- }
+ if (hasRestriction || shouldStopUserOnSwitch()) {
+ Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
+ stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
+ null, null);
return;
}
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users",
- oldUserId);
+ }
+
+ // We didn't need to stop the parent, but perhaps one of its profiles needs to be stopped.
+ final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
+ oldUserId, /* enabledOnly= */ false);
+ final int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ final int profileUserId = profiles.get(i).id;
+ if (hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, profileUserId)) {
+ Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
+ synchronized (mLock) {
+ stopUsersLU(profileUserId,
+ /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+ }
}
- stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
- null, null);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f1eea72..951f676 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -127,8 +127,7 @@
private final Object mDeviceStateLock = new Object();
// Request to override default use of A2DP for media.
- @GuardedBy("mDeviceStateLock")
- private boolean mBluetoothA2dpEnabled;
+ private AtomicBoolean mBluetoothA2dpEnabled = new AtomicBoolean(false);
// lock always taken when accessing AudioService.mSetModeDeathHandlers
// TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
@@ -275,17 +274,8 @@
}
/*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
- synchronized (mDeviceStateLock) {
- if (mBluetoothA2dpEnabled == on) {
- return;
- }
- mBluetoothA2dpEnabled = on;
- mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
- sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- source);
- }
+ mBluetoothA2dpEnabled.set(on);
+ sendLMsgNoDelay(MSG_L_SET_FORCE_BT_A2DP_USE, SENDMSG_REPLACE, source);
}
/**
@@ -1223,16 +1213,8 @@
}
}
- /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
- synchronized (mDeviceStateLock) {
- return mBtHelper.isAvrcpAbsoluteVolumeSupported();
- }
- }
-
/*package*/ boolean isBluetoothA2dpOn() {
- synchronized (mDeviceStateLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mBluetoothA2dpEnabled.get();
}
/*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
@@ -1601,15 +1583,12 @@
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).append(" src:").append(source).toString();
- synchronized (mDeviceStateLock) {
- mBluetoothA2dpEnabled = on;
- mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
- onSetForceUse(
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- fromA2dp,
- eventSource);
- }
+ mBluetoothA2dpEnabled.set(on);
+ onSetForceUse(
+ AudioSystem.FOR_MEDIA,
+ on ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ fromA2dp,
+ eventSource);
}
/*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
@@ -1658,9 +1637,7 @@
}
/*package*/ boolean getBluetoothA2dpEnabled() {
- synchronized (mDeviceStateLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mBluetoothA2dpEnabled.get();
}
/*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
@@ -1821,9 +1798,12 @@
}
break;
case MSG_IIL_SET_FORCE_USE: // intended fall-through
- case MSG_IIL_SET_FORCE_BT_A2DP_USE:
- onSetForceUse(msg.arg1, msg.arg2,
- (msg.what == MSG_IIL_SET_FORCE_BT_A2DP_USE), (String) msg.obj);
+ onSetForceUse(msg.arg1, msg.arg2, false, (String) msg.obj);
+ break;
+ case MSG_L_SET_FORCE_BT_A2DP_USE:
+ int forcedUsage = mBluetoothA2dpEnabled.get()
+ ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+ onSetForceUse(AudioSystem.FOR_MEDIA, forcedUsage, true, (String) msg.obj);
break;
case MSG_REPORT_NEW_ROUTES:
case MSG_REPORT_NEW_ROUTES_A2DP:
@@ -1831,35 +1811,38 @@
mDeviceInventory.onReportNewRoutes();
}
break;
- case MSG_L_SET_BT_ACTIVE_DEVICE:
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
- && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) {
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
- + "received with null profile proxy: "
- + btInfo)).printLog(TAG));
- } else {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_SET_BT_ACTIVE_DEVICE");
+ case MSG_L_SET_BT_ACTIVE_DEVICE: {
+ final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ && !mBtHelper.isProfilePoxyConnected(btInfo.mProfile)) {
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+ "msg: MSG_L_SET_BT_ACTIVE_DEVICE "
+ + "received with null profile proxy: "
+ + btInfo)).printLog(TAG));
+ } else {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_SET_BT_ACTIVE_DEVICE");
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
(btInfo.mProfile
- != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ != BluetoothProfile.LE_AUDIO
+ || btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
- || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ || btInfo.mProfile
+ == BluetoothProfile.HEARING_AID) {
+ onUpdateCommunicationRouteClient(
+ isBluetoothScoRequested(),
"setBluetoothActiveDevice");
}
}
}
}
- break;
+ } break;
case MSG_BT_HEADSET_CNCT_FAILED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1883,11 +1866,11 @@
break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
mDeviceInventory.onBluetoothDeviceConfigChange(
btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
@@ -2098,7 +2081,7 @@
private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
private static final int MSG_IIL_SET_FORCE_USE = 4;
- private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+ private static final int MSG_L_SET_FORCE_BT_A2DP_USE = 5;
private static final int MSG_TOGGLE_HDMI = 6;
private static final int MSG_L_SET_BT_ACTIVE_DEVICE = 7;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
@@ -2295,7 +2278,7 @@
MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
- MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ MESSAGES_MUTE_MUSIC.add(MSG_L_SET_FORCE_BT_A2DP_USE);
}
private AtomicBoolean mMusicMuted = new AtomicBoolean(false);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 649b9ef..ed58c40 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -34,7 +34,7 @@
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
-import static android.media.audio.Flags.foregroundAudioControl;
+import static android.media.audio.Flags.roForegroundAudioControl;
import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
@@ -46,6 +46,7 @@
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
+import static com.android.media.audio.Flags.setStreamVolumeOrder;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -4538,8 +4539,11 @@
+ focusFreezeTestApi());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
- pw.println("\tandroid.media.audio.foregroundAudioControl:"
- + foregroundAudioControl());
+
+ pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
+ + setStreamVolumeOrder());
+ pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+ + roForegroundAudioControl());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -4705,6 +4709,30 @@
index = rescaleIndex(index * 10, streamType, streamTypeAlias);
+ if (setStreamVolumeOrder()) {
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (index < 0) {
+ index = streamState.getMaxIndex();
+ }
+ }
+ }
+
+ if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+ flags)) {
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+ // ada is non-null when called from setDeviceVolume,
+ // which shouldn't update the mute state
+ canChangeMuteAndUpdateController /*canChangeMute*/);
+ index = mStreamStates[streamType].getIndex(device);
+ }
+ }
+
if (streamTypeAlias == AudioSystem.STREAM_MUSIC
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
@@ -4738,26 +4766,28 @@
mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
}
- flags &= ~AudioManager.FLAG_FIXED_VOLUME;
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
- flags |= AudioManager.FLAG_FIXED_VOLUME;
+ if (!setStreamVolumeOrder()) {
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
- // volume is either 0 or max allowed for fixed volume devices
- if (index != 0) {
- index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
- if (index < 0) {
- index = streamState.getMaxIndex();
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (index < 0) {
+ index = streamState.getMaxIndex();
+ }
}
}
- }
- if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
- flags)) {
- onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
- // ada is non-null when called from setDeviceVolume,
- // which shouldn't update the mute state
- canChangeMuteAndUpdateController /*canChangeMute*/);
- index = mStreamStates[streamType].getIndex(device);
+ if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+ flags)) {
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+ // ada is non-null when called from setDeviceVolume,
+ // which shouldn't update the mute state
+ canChangeMuteAndUpdateController /*canChangeMute*/);
+ index = mStreamStates[streamType].getIndex(device);
+ }
}
synchronized (mHdmiClientLock) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0f3f807..f3a5fdb 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -235,10 +235,6 @@
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
}
- /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() {
- return (mA2dp != null && mAvrcpAbsVolSupported);
- }
-
/*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) {
mAvrcpAbsVolSupported = supported;
Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported);
@@ -648,8 +644,6 @@
}
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized boolean isProfilePoxyConnected(int profile) {
switch (profile) {
case BluetoothProfile.HEADSET:
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 14f3120..7df63b1 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -869,9 +869,7 @@
}
if (faceAidlInstances != null && faceAidlInstances.length > 0) {
- mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
- name -> IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances);
}
if (faceService != null) {
@@ -909,9 +907,7 @@
}
if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
- mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
- name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances);
}
if (fingerprintService != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 0c3dfa7..eb78fe6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -23,6 +23,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -135,6 +136,14 @@
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
+ try {
+ mClientMonitor.getListener().onError(mClientMonitor.getSensorId(),
+ mClientMonitor.getCookie(), BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when trying to send error in cancel "
+ + "watchdog.");
+ }
getWrappedCallback(mOnStartCallback)
.onClientFinished(mClientMonitor, false /* success */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7ee2a7a..1037124 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -729,8 +729,8 @@
private List<ServiceProvider> getProviders(
FaceSensorConfigurations faceSensorConfigurations) {
final List<ServiceProvider> providers = new ArrayList<>();
- final Pair<String, SensorProps[]> filteredSensorProps =
- filterAvailableHalInstances(faceSensorConfigurations);
+ final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+ faceSensorConfigurations);
providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
faceSensorConfigurations.getResetLockoutRequiresChallenge()));
return providers;
@@ -739,28 +739,36 @@
@NonNull
private Pair<String, SensorProps[]> filterAvailableHalInstances(
FaceSensorConfigurations faceSensorConfigurations) {
- Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+ String finalSensorInstance = faceSensorConfigurations.getSensorInstance();
if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance,
+ faceSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
}
-
- final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
- .getSensorPairForInstance("virtual");
-
- if (Utils.isVirtualEnabled(getContext())) {
- if (virtualSensorProps != null) {
- return virtualSensorProps;
+ final String virtualInstance = "virtual";
+ final boolean isVirtualHalPresent =
+ faceSensorConfigurations.doesInstanceExist(virtualInstance);
+ if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (isVirtualHalPresent) {
+ return new Pair<>(virtualInstance,
+ faceSensorConfigurations.getSensorPropForInstance(virtualInstance));
} else {
Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance,
+ faceSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
}
} else {
- if (virtualSensorProps != null) {
- return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+ if (isVirtualHalPresent) {
+ final String notAVirtualInstance =
+ faceSensorConfigurations.getSensorNameNotForInstance(virtualInstance);
+ if (notAVirtualInstance != null) {
+ return new Pair<>(notAVirtualInstance, faceSensorConfigurations
+ .getSensorPropForInstance(notAVirtualInstance));
+ }
}
}
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance, faceSensorConfigurations
+ .getSensorPropForInstance(finalSensorInstance));
}
private Pair<List<FaceSensorPropertiesInternal>, List<String>>
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1ba1213..2dc03ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1128,27 +1128,36 @@
@NonNull
private Pair<String, SensorProps[]> filterAvailableHalInstances(
FingerprintSensorConfigurations fingerprintSensorConfigurations) {
- Pair<String, SensorProps[]> finalSensorPair =
- fingerprintSensorConfigurations.getSensorPair();
+ final String finalSensorInstance = fingerprintSensorConfigurations.getSensorInstance();
if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance,
+ fingerprintSensorConfigurations.getSensorPropForInstance(finalSensorInstance));
}
-
- final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
- .getSensorPairForInstance("virtual");
+ final String virtualInstance = "virtual";
+ final boolean isVirtualHalPresent =
+ fingerprintSensorConfigurations.doesInstanceExist(virtualInstance);
if (Utils.isVirtualEnabled(getContext())) {
- if (virtualSensorPropsPair != null) {
- return virtualSensorPropsPair;
+ if (isVirtualHalPresent) {
+ return new Pair<>(virtualInstance,
+ fingerprintSensorConfigurations.getSensorPropForInstance(virtualInstance));
} else {
Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance,
+ fingerprintSensorConfigurations.getSensorPropForInstance(
+ finalSensorInstance));
}
} else {
- if (virtualSensorPropsPair != null) {
- return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+ if (isVirtualHalPresent) {
+ final String notAVirtualInstance = fingerprintSensorConfigurations
+ .getSensorNameNotForInstance(virtualInstance);
+ if (notAVirtualInstance != null) {
+ return new Pair<>(notAVirtualInstance, fingerprintSensorConfigurations
+ .getSensorPropForInstance(notAVirtualInstance));
+ }
}
}
- return finalSensorPair;
+ return new Pair<>(finalSensorInstance, fingerprintSensorConfigurations
+ .getSensorPropForInstance(finalSensorInstance));
}
private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index e2ae3de..e8394d4 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -399,7 +399,7 @@
final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE);
final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE);
- return new DeviceStateInfo(supportedStates, baseState,
+ return new DeviceStateInfo(new ArrayList<>(supportedStates), baseState,
createMergedDeviceState(currentState, baseState));
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1546010..4aab9d2 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -29,6 +29,9 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -37,19 +40,20 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
import android.util.EventLog;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.Clock;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import java.io.PrintWriter;
@@ -64,6 +68,8 @@
public class AutomaticBrightnessController {
private static final String TAG = "AutomaticBrightnessController";
+ private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+
public static final int AUTO_BRIGHTNESS_ENABLED = 1;
public static final int AUTO_BRIGHTNESS_DISABLED = 2;
public static final int AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE = 3;
@@ -81,20 +87,32 @@
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
+ // How long the current sensor reading is assumed to be valid beyond the current time.
+ // This provides a bit of prediction, as well as ensures that the weight for the last sample is
+ // non-zero, which in turn ensures that the total weight is non-zero.
+ private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
+
// Debounce for sampling user-initiated changes in display brightness to ensure
// the user is satisfied with the result before storing the sample.
private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
- private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 1;
- private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 2;
- private static final int MSG_UPDATE_FOREGROUND_APP = 3;
- private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 4;
- private static final int MSG_RUN_UPDATE = 5;
- private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 6;
+ private static final int MSG_UPDATE_AMBIENT_LUX = 1;
+ private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
+ private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3;
+ private static final int MSG_UPDATE_FOREGROUND_APP = 4;
+ private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5;
+ private static final int MSG_RUN_UPDATE = 6;
+ private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7;
// Callbacks for requesting updates to the display's power state
private final Callbacks mCallbacks;
+ // The sensor manager.
+ private final SensorManager mSensorManager;
+
+ // The light sensor, or null if not available or needed.
+ private final Sensor mLightSensor;
+
// The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
@NonNull
private BrightnessMappingStrategy mCurrentBrightnessMapper;
@@ -109,21 +127,94 @@
// How much to scale doze brightness by (should be (0, 1.0]).
private final float mDozeScaleFactor;
+ // Initial light sensor event rate in milliseconds.
+ private final int mInitialLightSensorRate;
+
+ // Steady-state light sensor event rate in milliseconds.
+ private final int mNormalLightSensorRate;
+
+ // The current light sensor event rate in milliseconds.
+ private int mCurrentLightSensorRate;
+
+ // Stability requirements in milliseconds for accepting a new brightness level. This is used
+ // for debouncing the light sensor. Different constants are used to debounce the light sensor
+ // when adapting to brighter or darker environments. This parameter controls how quickly
+ // brightness changes occur in response to an observed change in light level that exceeds the
+ // hysteresis threshold.
+ private final long mBrighteningLightDebounceConfig;
+ private final long mDarkeningLightDebounceConfig;
+ private final long mBrighteningLightDebounceConfigIdle;
+ private final long mDarkeningLightDebounceConfigIdle;
+
+ // If true immediately after the screen is turned on the controller will try to adjust the
+ // brightness based on the current sensor reads. If false, the controller will collect more data
+ // and only then decide whether to change brightness.
+ private final boolean mResetAmbientLuxAfterWarmUpConfig;
+
+ // Period of time in which to consider light samples for a short/long-term estimate of ambient
+ // light in milliseconds.
+ private final int mAmbientLightHorizonLong;
+ private final int mAmbientLightHorizonShort;
+
+ // The intercept used for the weighting calculation. This is used in order to keep all possible
+ // weighting values positive.
+ private final int mWeightingIntercept;
+
// Configuration object for determining thresholds to change brightness dynamically
+ private final HysteresisLevels mAmbientBrightnessThresholds;
private final HysteresisLevels mScreenBrightnessThresholds;
+ private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
private final HysteresisLevels mScreenBrightnessThresholdsIdle;
private boolean mLoggingEnabled;
+
+ // Amount of time to delay auto-brightness after screen on while waiting for
+ // the light sensor to warm-up in milliseconds.
+ // May be 0 if no warm-up is required.
+ private int mLightSensorWarmUpTimeConfig;
+
+ // Set to true if the light sensor is enabled.
+ private boolean mLightSensorEnabled;
+
+ // The time when the light sensor was enabled.
+ private long mLightSensorEnableTime;
+
// The currently accepted nominal ambient light level.
private float mAmbientLux;
+
+ // The last calculated ambient light level (long time window).
+ private float mSlowAmbientLux;
+
+ // The last calculated ambient light level (short time window).
+ private float mFastAmbientLux;
+
+ // The last ambient lux value prior to passing the darkening or brightening threshold.
+ private float mPreThresholdLux;
+
// True if mAmbientLux holds a valid value.
private boolean mAmbientLuxValid;
+
+ // The ambient light level threshold at which to brighten or darken the screen.
+ private float mAmbientBrighteningThreshold;
+ private float mAmbientDarkeningThreshold;
+
// The last brightness value prior to passing the darkening or brightening threshold.
private float mPreThresholdBrightness;
// The screen brightness threshold at which to brighten or darken the screen.
private float mScreenBrighteningThreshold;
private float mScreenDarkeningThreshold;
+ // The most recent light sample.
+ private float mLastObservedLux = INVALID_LUX;
+
+ // The time of the most light recent sample.
+ private long mLastObservedLuxTime;
+
+ // The number of light samples collected since the light sensor was enabled.
+ private int mRecentLightSamples;
+
+ // A ring buffer containing all of the recent ambient light sensor readings.
+ private AmbientLightRingBuffer mAmbientLightRingBuffer;
// The handler
private AutomaticBrightnessHandler mHandler;
@@ -182,55 +273,88 @@
private Context mContext;
private int mState = AUTO_BRIGHTNESS_DISABLED;
- private final Clock mClock;
+ private Clock mClock;
private final Injector mInjector;
- private final LightSensorController mLightSensorController;
-
- AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager,
+ AutomaticBrightnessController(Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
- this(new Injector(), callbacks, looper,
- brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor,
- screenBrightnessThresholds,
+ this(new Injector(), callbacks, looper, sensorManager, lightSensor,
+ brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
+ dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
- brightnessThrottler, userLux, userNits,
- new LightSensorController(sensorManager, looper, displayId, config),
- brightnessClamperController
+ brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+ userNits, brightnessClamperController
);
}
@VisibleForTesting
AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessRangeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- LightSensorController lightSensorController,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
mInjector = injector;
mClock = injector.createClock();
mContext = context;
mCallbacks = callbacks;
+ mSensorManager = sensorManager;
mCurrentBrightnessMapper = brightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT);
mScreenBrightnessRangeMinimum = brightnessMin;
mScreenBrightnessRangeMaximum = brightnessMax;
+ mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
mDozeScaleFactor = dozeScaleFactor;
+ mNormalLightSensorRate = lightSensorRate;
+ mInitialLightSensorRate = initialLightSensorRate;
+ mCurrentLightSensorRate = -1;
+ mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
+ mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
+ mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle;
+ mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle;
+ mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
+ mAmbientLightHorizonLong = ambientLightHorizonLong;
+ mAmbientLightHorizonShort = ambientLightHorizonShort;
+ mWeightingIntercept = ambientLightHorizonLong;
+ mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+ mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
mScreenBrightnessThresholds = screenBrightnessThresholds;
mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle;
mShortTermModel = new ShortTermModel();
mPausedShortTermModel = new ShortTermModel();
mHandler = new AutomaticBrightnessHandler(looper);
- mLightSensorController = lightSensorController;
- mLightSensorController.setListener(this::setAmbientLux);
+ mAmbientLightRingBuffer =
+ new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock);
+
+ if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
+ mLightSensor = lightSensor;
+ }
+
mActivityTaskManager = ActivityTaskManager.getService();
mPackageManager = mContext.getPackageManager();
mTaskStackListener = new TaskStackListenerImpl();
@@ -282,13 +406,13 @@
if (brightnessEvent != null) {
brightnessEvent.setLux(
mAmbientLuxValid ? mAmbientLux : PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ brightnessEvent.setPreThresholdLux(mPreThresholdLux);
brightnessEvent.setPreThresholdBrightness(mPreThresholdBrightness);
brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
- mLightSensorController.updateBrightnessEvent(brightnessEvent);
}
if (!mAmbientLuxValid) {
@@ -311,22 +435,21 @@
*/
public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
BrightnessEvent brightnessEvent) {
- float lastObservedLux = mLightSensorController.getLastObservedLux();
- if (lastObservedLux == INVALID_LUX) {
+ if (mLastObservedLux == INVALID_LUX) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- float brightness = mCurrentBrightnessMapper.getBrightness(lastObservedLux,
+ float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
mForegroundAppPackageName, mForegroundAppCategory);
if (shouldApplyDozeScaleFactor()) {
brightness *= mDozeScaleFactor;
}
if (brightnessEvent != null) {
- brightnessEvent.setLux(lastObservedLux);
+ brightnessEvent.setLux(mLastObservedLux);
brightnessEvent.setRecommendedBrightness(brightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
- | (lastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
+ | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
@@ -378,7 +501,6 @@
public void stop() {
setLightSensorEnabled(false);
- mLightSensorController.stop();
}
public boolean hasUserDataPoints() {
@@ -407,6 +529,14 @@
return mAmbientLux;
}
+ float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
private boolean setDisplayPolicy(int policy) {
if (mDisplayPolicy == policy) {
return false;
@@ -485,13 +615,36 @@
pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
pw.println(" mDozeScaleFactor=" + mDozeScaleFactor);
+ pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate);
+ pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate);
+ pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+ pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
+ pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
+ pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
+ pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
+ pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
+ pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
+ pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
+ pw.println(" mWeightingIntercept=" + mWeightingIntercept);
+
pw.println();
pw.println("Automatic Brightness Controller State:");
+ pw.println(" mLightSensor=" + mLightSensor);
+ pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
+ pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
+ pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate);
pw.println(" mAmbientLux=" + mAmbientLux);
pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
+ pw.println(" mPreThresholdLux=" + mPreThresholdLux);
pw.println(" mPreThresholdBrightness=" + mPreThresholdBrightness);
+ pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
+ pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
pw.println(" mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
pw.println(" mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
+ pw.println(" mLastObservedLux=" + mLastObservedLux);
+ pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
+ pw.println(" mRecentLightSamples=" + mRecentLightSamples);
+ pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness);
pw.println(" mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
pw.println(" mShortTermModel=");
@@ -520,21 +673,22 @@
}
pw.println();
+ pw.println(" mAmbientBrightnessThresholds=");
+ mAmbientBrightnessThresholds.dump(pw);
pw.println(" mScreenBrightnessThresholds=");
mScreenBrightnessThresholds.dump(pw);
pw.println(" mScreenBrightnessThresholdsIdle=");
mScreenBrightnessThresholdsIdle.dump(pw);
-
- pw.println();
- mLightSensorController.dump(pw);
+ pw.println(" mAmbientBrightnessThresholdsIdle=");
+ mAmbientBrightnessThresholdsIdle.dump(pw);
}
public float[] getLastSensorValues() {
- return mLightSensorController.getLastSensorValues();
+ return mAmbientLightRingBuffer.getAllLuxValues();
}
public long[] getLastSensorTimestamps() {
- return mLightSensorController.getLastSensorTimestamps();
+ return mAmbientLightRingBuffer.getAllTimestamps();
}
private String configStateToString(int state) {
@@ -551,33 +705,273 @@
}
private boolean setLightSensorEnabled(boolean enable) {
- if (enable && mLightSensorController.enableLightSensorIfNeeded()) {
- registerForegroundAppUpdater();
- return true;
- } else if (!enable && mLightSensorController.disableLightSensorIfNeeded()) {
+ if (enable) {
+ if (!mLightSensorEnabled) {
+ mLightSensorEnabled = true;
+ mLightSensorEnableTime = mClock.uptimeMillis();
+ mCurrentLightSensorRate = mInitialLightSensorRate;
+ registerForegroundAppUpdater();
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ mCurrentLightSensorRate * 1000, mHandler);
+ return true;
+ }
+ } else if (mLightSensorEnabled) {
+ mLightSensorEnabled = false;
+ mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
+ if (!mAmbientLuxValid) {
+ mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mRawScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mAmbientLuxValid = mLightSensorController.hasValidAmbientLux();
+ mRecentLightSamples = 0;
+ mAmbientLightRingBuffer.clear();
+ mCurrentLightSensorRate = -1;
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
unregisterForegroundAppUpdater();
+ mSensorManager.unregisterListener(mLightSensorListener);
}
return false;
}
+ private void handleLightSensorEvent(long time, float lux) {
+ Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
+ mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
+
+ if (mAmbientLightRingBuffer.size() == 0) {
+ // switch to using the steady-state sample rate after grabbing the initial light sample
+ adjustLightSensorRate(mNormalLightSensorRate);
+ }
+ applyLightSensorMeasurement(time, lux);
+ updateAmbientLux(time);
+ }
+
+ private void applyLightSensorMeasurement(long time, float lux) {
+ mRecentLightSamples++;
+ mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
+ mAmbientLightRingBuffer.push(time, lux);
+
+ // Remember this sample value.
+ mLastObservedLux = lux;
+ mLastObservedLuxTime = time;
+ }
+
+ private void adjustLightSensorRate(int lightSensorRate) {
+ // if the light sensor rate changed, update the sensor listener
+ if (lightSensorRate != mCurrentLightSensorRate) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "adjustLightSensorRate: " +
+ "previousRate=" + mCurrentLightSensorRate + ", " +
+ "currentRate=" + lightSensorRate);
+ }
+ mCurrentLightSensorRate = lightSensorRate;
+ mSensorManager.unregisterListener(mLightSensorListener);
+ mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+ lightSensorRate * 1000, mHandler);
+ }
+ }
+
private boolean setAutoBrightnessAdjustment(float adjustment) {
return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
}
private void setAmbientLux(float lux) {
- // called by LightSensorController.setAmbientLux
- mAmbientLuxValid = true;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "setAmbientLux(" + lux + ")");
+ }
+ if (lux < 0) {
+ Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0");
+ lux = 0;
+ }
mAmbientLux = lux;
+ if (isInIdleMode()) {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
+ } else {
+ mAmbientBrighteningThreshold =
+ mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold =
+ mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
+ }
mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
mBrightnessClamperController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
mShortTermModel.maybeReset(mAmbientLux);
- updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ }
+
+ private float calculateAmbientLux(long now, long horizon) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")");
+ }
+ final int N = mAmbientLightRingBuffer.size();
+ if (N == 0) {
+ Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
+ return -1;
+ }
+
+ // Find the first measurement that is just outside of the horizon.
+ int endIndex = 0;
+ final long horizonStartTime = now - horizon;
+ for (int i = 0; i < N-1; i++) {
+ if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
+ endIndex++;
+ } else {
+ break;
+ }
+ }
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" +
+ mAmbientLightRingBuffer.getTime(endIndex) + ", " +
+ mAmbientLightRingBuffer.getLux(endIndex) + ")");
+ }
+ float sum = 0;
+ float totalWeight = 0;
+ long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
+ for (int i = N - 1; i >= endIndex; i--) {
+ long eventTime = mAmbientLightRingBuffer.getTime(i);
+ if (i == endIndex && eventTime < horizonStartTime) {
+ // If we're at the final value, make sure we only consider the part of the sample
+ // within our desired horizon.
+ eventTime = horizonStartTime;
+ }
+ final long startTime = eventTime - now;
+ float weight = calculateWeight(startTime, endTime);
+ float lux = mAmbientLightRingBuffer.getLux(i);
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " +
+ "lux=" + lux + ", " +
+ "weight=" + weight);
+ }
+ totalWeight += weight;
+ sum += lux * weight;
+ endTime = startTime;
+ }
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "calculateAmbientLux: " +
+ "totalWeight=" + totalWeight + ", " +
+ "newAmbientLux=" + (sum / totalWeight));
+ }
+ return sum / totalWeight;
+ }
+
+ private float calculateWeight(long startDelta, long endDelta) {
+ return weightIntegral(endDelta) - weightIntegral(startDelta);
+ }
+
+ // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
+ // horizon we're looking at and provides a non-linear weighting for light samples.
+ private float weightIntegral(long x) {
+ return x * (x * 0.5f + mWeightingIntercept);
+ }
+
+ private long nextAmbientLightBrighteningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + (isInIdleMode()
+ ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig);
+ }
+
+ private long nextAmbientLightDarkeningTransition(long time) {
+ final int N = mAmbientLightRingBuffer.size();
+ long earliestValidTime = time;
+ for (int i = N - 1; i >= 0; i--) {
+ if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
+ break;
+ }
+ earliestValidTime = mAmbientLightRingBuffer.getTime(i);
+ }
+ return earliestValidTime + (isInIdleMode()
+ ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig);
+ }
+
+ private void updateAmbientLux() {
+ long time = mClock.uptimeMillis();
+ mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong);
+ updateAmbientLux(time);
+ }
+
+ private void updateAmbientLux(long time) {
+ // If the light sensor was just turned on then immediately update our initial
+ // estimate of the current ambient light level.
+ if (!mAmbientLuxValid) {
+ final long timeWhenSensorWarmedUp =
+ mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+ if (time < timeWhenSensorWarmedUp) {
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: "
+ + "time=" + time + ", "
+ + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX,
+ timeWhenSensorWarmedUp);
+ return;
+ }
+ setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort));
+ mAmbientLuxValid = true;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Initializing: " +
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
+ "mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ }
+
+ long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term
+ // change in lighting conditions, and a fast ambient lux to determine what the new
+ // brightness situation is since the slow lux can be quite slow to converge.
+ //
+ // Note that both values need to be checked for sufficient change before updating the
+ // proposed ambient light value since the slow value might be sufficiently far enough away
+ // from the fast value to cause a recalculation while its actually just converging on
+ // the fast value still.
+ mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+ mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+
+ if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+ && mFastAmbientLux >= mAmbientBrighteningThreshold
+ && nextBrightenTransition <= time)
+ || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+ && mFastAmbientLux <= mAmbientDarkeningThreshold
+ && nextDarkenTransition <= time)) {
+ mPreThresholdLux = mAmbientLux;
+ setAmbientLux(mFastAmbientLux);
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: "
+ + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
+ + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ + "mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
+ nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
+ nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
+ }
+ long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
+ // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
+ // exceed the necessary threshold, then it's possible we'll get a transition time prior to
+ // now. Rather than continually checking to see whether the weighted lux exceeds the
+ // threshold, schedule an update for when we'd normally expect another light sample, which
+ // should be enough time to decide whether we should actually transition to the new
+ // weighted ambient lux or not.
+ nextTransitionTime =
+ nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate;
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " +
+ nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
+ }
+ mHandler.sendEmptyMessageAtTime(MSG_UPDATE_AMBIENT_LUX, nextTransitionTime);
}
private void updateAutoBrightness(boolean sendUpdate, boolean isManuallySet) {
@@ -678,7 +1072,8 @@
if (mLoggingEnabled) {
Slog.d(TAG, "Auto-brightness adjustment changed by user: "
+ "lux=" + mAmbientLux + ", "
- + "brightness=" + mScreenAutoBrightness);
+ + "brightness=" + mScreenAutoBrightness + ", "
+ + "ring=" + mAmbientLightRingBuffer);
}
EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
@@ -808,7 +1203,6 @@
if (mode == AUTO_BRIGHTNESS_MODE_IDLE
|| mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
switchModeAndShortTermModels(mode);
- mLightSensorController.setIdleMode(isInIdleMode());
} else {
resetShortTermModel();
mCurrentBrightnessMapper = mBrightnessMappingStrategyMap.get(mode);
@@ -965,6 +1359,10 @@
updateAutoBrightness(true /*sendUpdate*/, false /*isManuallySet*/);
break;
+ case MSG_UPDATE_AMBIENT_LUX:
+ updateAmbientLux();
+ break;
+
case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
collectBrightnessAdjustmentSample();
break;
@@ -988,6 +1386,22 @@
}
}
+ private final SensorEventListener mLightSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (mLightSensorEnabled) {
+ final long time = mClock.uptimeMillis();
+ final float lux = event.values[0];
+ handleLightSensorEvent(time, lux);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Not used.
+ }
+ };
+
// Call back whenever the tasks stack changes, which includes tasks being created, removed, and
// moving to top.
class TaskStackListenerImpl extends TaskStackListener {
@@ -1002,13 +1416,192 @@
void updateBrightness();
}
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ /**
+ * A ring buffer of ambient light measurements sorted by time.
+ *
+ * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted
+ * from oldest to newest.
+ */
+ private static final class AmbientLightRingBuffer {
+ // Proportional extra capacity of the buffer beyond the expected number of light samples
+ // in the horizon
+ private static final float BUFFER_SLACK = 1.5f;
+ private float[] mRingLux;
+ private long[] mRingTime;
+ private int mCapacity;
+
+ // The first valid element and the next open slot.
+ // Note that if mCount is zero then there are no valid elements.
+ private int mStart;
+ private int mEnd;
+ private int mCount;
+ Clock mClock;
+
+ public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) {
+ if (lightSensorRate <= 0) {
+ throw new IllegalArgumentException("lightSensorRate must be above 0");
+ }
+ mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
+ mRingLux = new float[mCapacity];
+ mRingTime = new long[mCapacity];
+ mClock = clock;
+ }
+
+ public float getLux(int index) {
+ return mRingLux[offsetOf(index)];
+ }
+
+ public float[] getAllLuxValues() {
+ float[] values = new float[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingLux, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
+ public long getTime(int index) {
+ return mRingTime[offsetOf(index)];
+ }
+
+ public long[] getAllTimestamps() {
+ long[] values = new long[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingTime, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
+ public void push(long time, float lux) {
+ int next = mEnd;
+ if (mCount == mCapacity) {
+ int newSize = mCapacity * 2;
+
+ float[] newRingLux = new float[newSize];
+ long[] newRingTime = new long[newSize];
+ int length = mCapacity - mStart;
+ System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
+ System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
+ if (mStart != 0) {
+ System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
+ System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
+ }
+ mRingLux = newRingLux;
+ mRingTime = newRingTime;
+
+ next = mCapacity;
+ mCapacity = newSize;
+ mStart = 0;
+ }
+ mRingTime[next] = time;
+ mRingLux[next] = lux;
+ mEnd = next + 1;
+ if (mEnd == mCapacity) {
+ mEnd = 0;
+ }
+ mCount++;
+ }
+
+ public void prune(long horizon) {
+ if (mCount == 0) {
+ return;
+ }
+
+ while (mCount > 1) {
+ int next = mStart + 1;
+ if (next >= mCapacity) {
+ next -= mCapacity;
+ }
+ if (mRingTime[next] > horizon) {
+ // Some light sensors only produce data upon a change in the ambient light
+ // levels, so we need to consider the previous measurement as the ambient light
+ // level for all points in time up until we receive a new measurement. Thus, we
+ // always want to keep the youngest element that would be removed from the
+ // buffer and just set its measurement time to the horizon time since at that
+ // point it is the ambient light level, and to remove it would be to drop a
+ // valid data point within our horizon.
+ break;
+ }
+ mStart = next;
+ mCount -= 1;
+ }
+
+ if (mRingTime[mStart] < horizon) {
+ mRingTime[mStart] = horizon;
+ }
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public void clear() {
+ mStart = 0;
+ mEnd = 0;
+ mCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append('[');
+ for (int i = 0; i < mCount; i++) {
+ final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis();
+ if (i != 0) {
+ buf.append(", ");
+ }
+ buf.append(getLux(i));
+ buf.append(" / ");
+ buf.append(next - getTime(i));
+ buf.append("ms");
+ }
+ buf.append(']');
+ return buf.toString();
+ }
+
+ private int offsetOf(int index) {
+ if (index >= mCount || index < 0) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ index += mStart;
+ if (index >= mCapacity) {
+ index -= mCapacity;
+ }
+ return index;
+ }
+ }
+
public static class Injector {
public Handler getBackgroundThreadHandler() {
return BackgroundThread.getHandler();
}
Clock createClock() {
- return Clock.SYSTEM_CLOCK;
+ return SystemClock::uptimeMillis;
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 641b6a2..61ecb93 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1930,7 +1930,6 @@
*
* @return true if even dimmer mode is enabled
*/
- @VisibleForTesting
public boolean isEvenDimmerAvailable() {
return mEvenDimmerBrightnessData != null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ba21a32..434985e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -464,10 +464,11 @@
// May be used outside of the lock but only on the handler thread.
private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
- // Pending callback records indexed by calling process uid.
+ // Pending callback records indexed by calling process uid and pid.
// Must be used outside of the lock mSyncRoot and should be selflocked.
@GuardedBy("mPendingCallbackSelfLocked")
- public final SparseArray<PendingCallback> mPendingCallbackSelfLocked = new SparseArray<>();
+ public final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
+ new SparseArray<>();
// Temporary viewports, used when sending new viewport information to the
// input system. May be used outside of the lock but only on the handler thread.
@@ -1011,8 +1012,8 @@
}
// Do we care about this uid?
- PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
- if (pendingCallback == null) {
+ SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(uid);
+ if (pendingCallbacks == null) {
return;
}
@@ -1020,7 +1021,12 @@
if (DEBUG) {
Slog.d(TAG, "Uid " + uid + " becomes " + importance);
}
- pendingCallback.sendPendingDisplayEvent();
+ for (int i = 0; i < pendingCallbacks.size(); i++) {
+ PendingCallback pendingCallback = pendingCallbacks.valueAt(i);
+ if (pendingCallback != null) {
+ pendingCallback.sendPendingDisplayEvent();
+ }
+ }
mPendingCallbackSelfLocked.delete(uid);
}
}
@@ -3193,16 +3199,23 @@
for (int i = 0; i < mTempCallbacks.size(); i++) {
CallbackRecord callbackRecord = mTempCallbacks.get(i);
final int uid = callbackRecord.mUid;
+ final int pid = callbackRecord.mPid;
if (isUidCached(uid)) {
// For cached apps, save the pending event until it becomes non-cached
synchronized (mPendingCallbackSelfLocked) {
- PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(
+ uid);
if (extraLogging(callbackRecord.mPackageName)) {
- Slog.i(TAG,
- "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+ Slog.i(TAG, "Uid is cached: " + uid
+ + ", pendingCallbacks: " + pendingCallbacks);
}
+ if (pendingCallbacks == null) {
+ pendingCallbacks = new SparseArray<>();
+ mPendingCallbackSelfLocked.put(uid, pendingCallbacks);
+ }
+ PendingCallback pendingCallback = pendingCallbacks.get(pid);
if (pendingCallback == null) {
- mPendingCallbackSelfLocked.put(uid,
+ pendingCallbacks.put(pid,
new PendingCallback(callbackRecord, displayId, event));
} else {
pendingCallback.addDisplayEvent(displayId, event);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 043ff0e..b21a89a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -82,7 +82,6 @@
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
@@ -1050,13 +1049,102 @@
}
if (defaultModeBrightnessMapper != null) {
+ // Ambient Lux - Active Mode Brightness Thresholds
+ float[] ambientBrighteningThresholds =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentages();
+ float[] ambientDarkeningThresholds =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentages();
+ float[] ambientBrighteningLevels =
+ mDisplayDeviceConfig.getAmbientBrighteningLevels();
+ float[] ambientDarkeningLevels =
+ mDisplayDeviceConfig.getAmbientDarkeningLevels();
+ float ambientDarkeningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+ float ambientBrighteningMinThreshold =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
+ HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
+ ambientBrighteningMinThreshold);
+
// Display - Active Mode Brightness Thresholds
- HysteresisLevels screenBrightnessThresholds =
- mInjector.getBrightnessThresholdsHysteresisLevels(mDisplayDeviceConfig);
+ float[] screenBrighteningThresholds =
+ mDisplayDeviceConfig.getScreenBrighteningPercentages();
+ float[] screenDarkeningThresholds =
+ mDisplayDeviceConfig.getScreenDarkeningPercentages();
+ float[] screenBrighteningLevels =
+ mDisplayDeviceConfig.getScreenBrighteningLevels();
+ float[] screenDarkeningLevels =
+ mDisplayDeviceConfig.getScreenDarkeningLevels();
+ float screenDarkeningMinThreshold =
+ mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+ float screenBrighteningMinThreshold =
+ mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
+ HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds,
+ screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
+ screenBrighteningMinThreshold, true);
+
+ // Ambient Lux - Idle Screen Brightness Thresholds
+ float ambientDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+ float ambientBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+ float[] ambientBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
+ float[] ambientDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
+ float[] ambientBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
+ float[] ambientDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
+ HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
+ ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
+ ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
+ ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
// Display - Idle Screen Brightness Thresholds
- HysteresisLevels screenBrightnessThresholdsIdle =
- mInjector.getBrightnessThresholdsIdleHysteresisLevels(mDisplayDeviceConfig);
+ float screenDarkeningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+ float screenBrighteningMinThresholdIdle =
+ mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+ float[] screenBrighteningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
+ float[] screenDarkeningThresholdsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
+ float[] screenBrighteningLevelsIdle =
+ mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
+ float[] screenDarkeningLevelsIdle =
+ mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
+ HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
+ screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
+ screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
+ screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+ long brighteningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounce();
+ long darkeningLightDebounce = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounce();
+ long brighteningLightDebounceIdle = mDisplayDeviceConfig
+ .getAutoBrightnessBrighteningLightDebounceIdle();
+ long darkeningLightDebounceIdle = mDisplayDeviceConfig
+ .getAutoBrightnessDarkeningLightDebounceIdle();
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
+ R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
+ R.integer.config_lightSensorWarmupTime);
+ int lightSensorRate = context.getResources().getInteger(
+ R.integer.config_autoBrightnessLightSensorRate);
+ int initialLightSensorRate = context.getResources().getInteger(
+ R.integer.config_autoBrightnessInitialLightSensorRate);
+ if (initialLightSensorRate == -1) {
+ initialLightSensorRate = lightSensorRate;
+ } else if (initialLightSensorRate > lightSensorRate) {
+ Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+ + initialLightSensorRate + ") to be less than or equal to "
+ + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
+ }
loadAmbientLightSensor();
// BrightnessTracker should only use one light sensor, we want to use the light sensor
@@ -1068,15 +1156,17 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
-
- LightSensorController.LightSensorControllerConfig config =
- mInjector.getLightSensorControllerConfig(context, mDisplayDeviceConfig);
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
- this, handler.getLooper(), mSensorManager, brightnessMappers,
- PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor,
- screenBrightnessThresholds, screenBrightnessThresholdsIdle,
- mContext, mBrightnessRangeController,
- mBrightnessThrottler, userLux, userNits, mDisplayId, config,
+ this, handler.getLooper(), mSensorManager, mLightSensor,
+ brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
+ initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
+ brighteningLightDebounceIdle, darkeningLightDebounceIdle,
+ autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
+ mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
mBrightnessClamperController);
mDisplayBrightnessController.setAutomaticBrightnessController(
mAutomaticBrightnessController);
@@ -3083,34 +3173,32 @@
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
- return new AutomaticBrightnessController(callbacks, looper, sensorManager,
- brightnessMappingStrategyMap, brightnessMin, brightnessMax, dozeScaleFactor,
- screenBrightnessThresholds, screenBrightnessThresholdsIdle, context,
- brightnessModeController, brightnessThrottler, userLux, userNits, displayId,
- config, brightnessClamperController);
- }
- LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig(
- Context context, DisplayDeviceConfig displayDeviceConfig) {
- return LightSensorController.LightSensorControllerConfig.create(
- context.getResources(), displayDeviceConfig);
- }
-
- HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) {
- return HysteresisLevels.getBrightnessThresholdsIdle(ddc);
- }
-
- HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) {
- return HysteresisLevels.getBrightnessThresholds(ddc);
+ return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
+ brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
+ brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
+ brighteningLightDebounceConfig, darkeningLightDebounceConfig,
+ brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle,
+ resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds,
+ screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
+ screenBrightnessThresholdsIdle, context, brightnessModeController,
+ brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
+ userNits, brightnessClamperController);
}
BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
@@ -3120,6 +3208,25 @@
AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
+ }
+
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+ return new HysteresisLevels(brighteningThresholdsPercentages,
+ darkeningThresholdsPercentages, brighteningThresholdLevels,
+ darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
+ potentialOldBrightnessRange);
+ }
+
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager,
Sensor lightSensor,
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index bb349e7..0521b8a 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,7 +18,6 @@
import android.util.Slog;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
@@ -53,8 +52,7 @@
* @param potentialOldBrightnessRange whether or not the values used could be from the old
* screen brightness range ie, between 1-255.
*/
- @VisibleForTesting
- public HysteresisLevels(float[] brighteningThresholdsPercentages,
+ HysteresisLevels(float[] brighteningThresholdsPercentages,
float[] darkeningThresholdsPercentages,
float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
float minDarkeningThreshold, float minBrighteningThreshold,
@@ -140,10 +138,7 @@
return levelArray;
}
- /**
- * Print the object's debug information into the given stream.
- */
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
pw.println(" mBrighteningThresholdsPercentages="
@@ -154,45 +149,4 @@
+ Arrays.toString(mDarkeningThresholdsPercentages));
pw.println(" mMinDarkening=" + mMinDarkening);
}
-
-
- /**
- * Creates hysteresis levels for Active Ambient Lux
- */
- public static HysteresisLevels getAmbientBrightnessThresholds(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getAmbientBrighteningPercentages(),
- ddc.getAmbientDarkeningPercentages(), ddc.getAmbientBrighteningLevels(),
- ddc.getAmbientDarkeningLevels(), ddc.getAmbientLuxDarkeningMinThreshold(),
- ddc.getAmbientLuxBrighteningMinThreshold());
- }
-
- /**
- * Creates hysteresis levels for Active Screen Brightness
- */
- public static HysteresisLevels getBrightnessThresholds(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getScreenBrighteningPercentages(),
- ddc.getScreenDarkeningPercentages(), ddc.getScreenBrighteningLevels(),
- ddc.getScreenDarkeningLevels(), ddc.getScreenDarkeningMinThreshold(),
- ddc.getScreenBrighteningMinThreshold(), true);
- }
-
- /**
- * Creates hysteresis levels for Idle Ambient Lux
- */
- public static HysteresisLevels getAmbientBrightnessThresholdsIdle(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getAmbientBrighteningPercentagesIdle(),
- ddc.getAmbientDarkeningPercentagesIdle(), ddc.getAmbientBrighteningLevelsIdle(),
- ddc.getAmbientDarkeningLevelsIdle(), ddc.getAmbientLuxDarkeningMinThresholdIdle(),
- ddc.getAmbientLuxBrighteningMinThresholdIdle());
- }
-
- /**
- * Creates hysteresis levels for Idle Screen Brightness
- */
- public static HysteresisLevels getBrightnessThresholdsIdle(DisplayDeviceConfig ddc) {
- return new HysteresisLevels(ddc.getScreenBrighteningPercentagesIdle(),
- ddc.getScreenDarkeningPercentagesIdle(), ddc.getScreenBrighteningLevelsIdle(),
- ddc.getScreenDarkeningLevelsIdle(), ddc.getScreenDarkeningMinThresholdIdle(),
- ddc.getScreenBrighteningMinThresholdIdle());
- }
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index a577e22..1dfe037 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -940,7 +940,9 @@
final float nits = backlightToNits(backlight);
final float sdrNits = backlightToNits(sdrBacklight);
- if (getFeatureFlags().isEvenDimmerEnabled()) {
+ if (getFeatureFlags().isEvenDimmerEnabled()
+ && mDisplayDeviceConfig != null
+ && mDisplayDeviceConfig.isEvenDimmerAvailable()) {
applyColorMatrixBasedDimming(brightnessState);
}
diff --git a/services/core/java/com/android/server/display/brightness/LightSensorController.java b/services/core/java/com/android/server/display/brightness/LightSensorController.java
deleted file mode 100644
index d82d698..0000000
--- a/services/core/java/com/android/server/display/brightness/LightSensorController.java
+++ /dev/null
@@ -1,868 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness;
-
-import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX;
-
-import android.annotation.Nullable;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.view.Display;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.Clock;
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.HysteresisLevels;
-import com.android.server.display.config.SensorData;
-import com.android.server.display.utils.SensorUtils;
-
-import java.io.PrintWriter;
-
-/**
- * Manages light sensor subscription and notifies its listeners about ambient lux changes based on
- * configuration
- */
-public class LightSensorController {
- // How long the current sensor reading is assumed to be valid beyond the current time.
- // This provides a bit of prediction, as well as ensures that the weight for the last sample is
- // non-zero, which in turn ensures that the total weight is non-zero.
- private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
-
- // Proportional extra capacity of the buffer beyond the expected number of light samples
- // in the horizon
- private static final float BUFFER_SLACK = 1.5f;
-
- private boolean mLoggingEnabled;
- private boolean mLightSensorEnabled;
- private long mLightSensorEnableTime;
- // The current light sensor event rate in milliseconds.
- private int mCurrentLightSensorRate = -1;
- // The number of light samples collected since the light sensor was enabled.
- private int mRecentLightSamples;
- private float mAmbientLux;
- // True if mAmbientLux holds a valid value.
- private boolean mAmbientLuxValid;
- // The last ambient lux value prior to passing the darkening or brightening threshold.
- private float mPreThresholdLux;
- // The most recent light sample.
- private float mLastObservedLux = INVALID_LUX;
- // The time of the most light recent sample.
- private long mLastObservedLuxTime;
- // The last calculated ambient light level (long time window).
- private float mSlowAmbientLux;
- // The last calculated ambient light level (short time window).
- private float mFastAmbientLux;
- private volatile boolean mIsIdleMode;
- // The ambient light level threshold at which to brighten or darken the screen.
- private float mAmbientBrighteningThreshold;
- private float mAmbientDarkeningThreshold;
-
- private final LightSensorControllerConfig mConfig;
-
- // The light sensor, or null if not available or needed.
- @Nullable
- private final Sensor mLightSensor;
-
- // A ring buffer containing all of the recent ambient light sensor readings.
- private final AmbientLightRingBuffer mAmbientLightRingBuffer;
-
- private final Injector mInjector;
-
- private final SensorEventListener mLightSensorListener = new SensorEventListener() {
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mLightSensorEnabled) {
- final long time = mClock.uptimeMillis();
- final float lux = event.values[0];
- handleLightSensorEvent(time, lux);
- }
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Not used.
- }
- };
-
- // Runnable used to delay ambient lux update when:
- // 1) update triggered before configured warm up time
- // 2) next brightening or darkening transition need to happen
- private final Runnable mAmbientLuxUpdater = this::updateAmbientLux;
-
- private final Clock mClock;
-
- private final Handler mHandler;
-
- private final String mTag;
-
- private LightSensorListener mListener;
-
- public LightSensorController(
- SensorManager sensorManager,
- Looper looper,
- int displayId,
- LightSensorControllerConfig config) {
- this(config, new RealInjector(sensorManager, displayId), new LightSensorHandler(looper));
- }
-
- @VisibleForTesting
- LightSensorController(
- LightSensorControllerConfig config,
- Injector injector,
- Handler handler) {
- if (config.mNormalLightSensorRate <= 0) {
- throw new IllegalArgumentException("lightSensorRate must be above 0");
- }
- mInjector = injector;
- int bufferInitialCapacity = (int) Math.ceil(
- config.mAmbientLightHorizonLong * BUFFER_SLACK / config.mNormalLightSensorRate);
- mClock = injector.getClock();
- mHandler = handler;
- mAmbientLightRingBuffer = new AmbientLightRingBuffer(bufferInitialCapacity, mClock);
- mConfig = config;
- mLightSensor = mInjector.getLightSensor(mConfig);
- mTag = mInjector.getTag();
- }
-
- public void setListener(LightSensorListener listener) {
- mListener = listener;
- }
-
- /**
- * @return true if sensor registered, false if sensor already registered
- */
- public boolean enableLightSensorIfNeeded() {
- if (!mLightSensorEnabled) {
- mLightSensorEnabled = true;
- mLightSensorEnableTime = mClock.uptimeMillis();
- mCurrentLightSensorRate = mConfig.mInitialLightSensorRate;
- mInjector.registerLightSensorListener(
- mLightSensorListener, mLightSensor, mCurrentLightSensorRate, mHandler);
- return true;
- }
- return false;
- }
-
- /**
- * @return true if sensor unregistered, false if sensor already unregistered
- */
- public boolean disableLightSensorIfNeeded() {
- if (mLightSensorEnabled) {
- mLightSensorEnabled = false;
- mAmbientLuxValid = !mConfig.mResetAmbientLuxAfterWarmUpConfig;
- if (!mAmbientLuxValid) {
- mPreThresholdLux = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- }
- mRecentLightSamples = 0;
- mAmbientLightRingBuffer.clear();
- mCurrentLightSensorRate = -1;
- mInjector.unregisterLightSensorListener(mLightSensorListener);
- return true;
- }
- return false;
- }
-
- public void setLoggingEnabled(boolean loggingEnabled) {
- mLoggingEnabled = loggingEnabled;
- }
-
- /**
- * Updates BrightnessEvent with LightSensorController details
- */
- public void updateBrightnessEvent(BrightnessEvent brightnessEvent) {
- brightnessEvent.setPreThresholdLux(mPreThresholdLux);
- }
-
- /**
- * Print the object's debug information into the given stream.
- */
- public void dump(PrintWriter pw) {
- pw.println("LightSensorController state:");
- pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
- pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
- pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate);
- pw.println(" mRecentLightSamples=" + mRecentLightSamples);
- pw.println(" mAmbientLux=" + mAmbientLux);
- pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
- pw.println(" mPreThresholdLux=" + mPreThresholdLux);
- pw.println(" mLastObservedLux=" + mLastObservedLux);
- pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
- pw.println(" mSlowAmbientLux=" + mSlowAmbientLux);
- pw.println(" mFastAmbientLux=" + mFastAmbientLux);
- pw.println(" mIsIdleMode=" + mIsIdleMode);
- pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
- pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
- pw.println(" mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
- pw.println(" mLightSensor=" + mLightSensor);
- mConfig.dump(pw);
- }
-
- /**
- * This method should be called when this LightSensorController is no longer in use
- * i.e. when corresponding display removed
- */
- public void stop() {
- mHandler.removeCallbacksAndMessages(null);
- disableLightSensorIfNeeded();
- }
-
- public void setIdleMode(boolean isIdleMode) {
- mIsIdleMode = isIdleMode;
- }
-
- /**
- * returns true if LightSensorController holds valid ambient lux value
- */
- public boolean hasValidAmbientLux() {
- return mAmbientLuxValid;
- }
-
- /**
- * returns all last observed sensor values
- */
- public float[] getLastSensorValues() {
- return mAmbientLightRingBuffer.getAllLuxValues();
- }
-
- /**
- * returns all last observed sensor event timestamps
- */
- public long[] getLastSensorTimestamps() {
- return mAmbientLightRingBuffer.getAllTimestamps();
- }
-
- public float getLastObservedLux() {
- return mLastObservedLux;
- }
-
- private void handleLightSensorEvent(long time, float lux) {
- Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
- mHandler.removeCallbacks(mAmbientLuxUpdater);
-
- if (mAmbientLightRingBuffer.size() == 0) {
- // switch to using the steady-state sample rate after grabbing the initial light sample
- adjustLightSensorRate(mConfig.mNormalLightSensorRate);
- }
- applyLightSensorMeasurement(time, lux);
- updateAmbientLux(time);
- }
-
- private void applyLightSensorMeasurement(long time, float lux) {
- mRecentLightSamples++;
- mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong);
- mAmbientLightRingBuffer.push(time, lux);
- // Remember this sample value.
- mLastObservedLux = lux;
- mLastObservedLuxTime = time;
- }
-
- private void adjustLightSensorRate(int lightSensorRate) {
- // if the light sensor rate changed, update the sensor listener
- if (lightSensorRate != mCurrentLightSensorRate) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "adjustLightSensorRate: "
- + "previousRate=" + mCurrentLightSensorRate + ", "
- + "currentRate=" + lightSensorRate);
- }
- mCurrentLightSensorRate = lightSensorRate;
- mInjector.unregisterLightSensorListener(mLightSensorListener);
- mInjector.registerLightSensorListener(
- mLightSensorListener, mLightSensor, lightSensorRate, mHandler);
- }
- }
-
- private void setAmbientLux(float lux) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "setAmbientLux(" + lux + ")");
- }
- if (lux < 0) {
- Slog.w(mTag, "Ambient lux was negative, ignoring and setting to 0");
- lux = 0;
- }
- mAmbientLux = lux;
-
- if (mIsIdleMode) {
- mAmbientBrighteningThreshold =
- mConfig.mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
- mAmbientDarkeningThreshold =
- mConfig.mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(lux);
- } else {
- mAmbientBrighteningThreshold =
- mConfig.mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
- mAmbientDarkeningThreshold =
- mConfig.mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
- }
-
- mListener.onAmbientLuxChange(mAmbientLux);
- }
-
- private float calculateAmbientLux(long now, long horizon) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux(" + now + ", " + horizon + ")");
- }
- final int size = mAmbientLightRingBuffer.size();
- if (size == 0) {
- Slog.e(mTag, "calculateAmbientLux: No ambient light readings available");
- return -1;
- }
-
- // Find the first measurement that is just outside of the horizon.
- int endIndex = 0;
- final long horizonStartTime = now - horizon;
- for (int i = 0; i < size - 1; i++) {
- if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
- endIndex++;
- } else {
- break;
- }
- }
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=("
- + mAmbientLightRingBuffer.getTime(endIndex) + ", "
- + mAmbientLightRingBuffer.getLux(endIndex) + ")");
- }
- float sum = 0;
- float totalWeight = 0;
- long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
- for (int i = size - 1; i >= endIndex; i--) {
- long eventTime = mAmbientLightRingBuffer.getTime(i);
- if (i == endIndex && eventTime < horizonStartTime) {
- // If we're at the final value, make sure we only consider the part of the sample
- // within our desired horizon.
- eventTime = horizonStartTime;
- }
- final long startTime = eventTime - now;
- float weight = calculateWeight(startTime, endTime);
- float lux = mAmbientLightRingBuffer.getLux(i);
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: "
- + "lux=" + lux + ", "
- + "weight=" + weight);
- }
- totalWeight += weight;
- sum += lux * weight;
- endTime = startTime;
- }
- if (mLoggingEnabled) {
- Slog.d(mTag, "calculateAmbientLux: "
- + "totalWeight=" + totalWeight + ", "
- + "newAmbientLux=" + (sum / totalWeight));
- }
- return sum / totalWeight;
- }
-
- private float calculateWeight(long startDelta, long endDelta) {
- return weightIntegral(endDelta) - weightIntegral(startDelta);
- }
-
- // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
- // horizon we're looking at and provides a non-linear weighting for light samples.
- private float weightIntegral(long x) {
- return x * (x * 0.5f + mConfig.mWeightingIntercept);
- }
-
- private long nextAmbientLightBrighteningTransition(long time) {
- final int size = mAmbientLightRingBuffer.size();
- long earliestValidTime = time;
- for (int i = size - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
- break;
- }
- earliestValidTime = mAmbientLightRingBuffer.getTime(i);
- }
- return earliestValidTime + (mIsIdleMode ? mConfig.mBrighteningLightDebounceConfigIdle
- : mConfig.mBrighteningLightDebounceConfig);
- }
-
- private long nextAmbientLightDarkeningTransition(long time) {
- final int size = mAmbientLightRingBuffer.size();
- long earliestValidTime = time;
- for (int i = size - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
- break;
- }
- earliestValidTime = mAmbientLightRingBuffer.getTime(i);
- }
- return earliestValidTime + (mIsIdleMode ? mConfig.mDarkeningLightDebounceConfigIdle
- : mConfig.mDarkeningLightDebounceConfig);
- }
-
- private void updateAmbientLux() {
- long time = mClock.uptimeMillis();
- mAmbientLightRingBuffer.prune(time - mConfig.mAmbientLightHorizonLong);
- updateAmbientLux(time);
- }
-
- private void updateAmbientLux(long time) {
- // If the light sensor was just turned on then immediately update our initial
- // estimate of the current ambient light level.
- if (!mAmbientLuxValid) {
- final long timeWhenSensorWarmedUp =
- mConfig.mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
- if (time < timeWhenSensorWarmedUp) {
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Sensor not ready yet: "
- + "time=" + time + ", "
- + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp);
- }
- mHandler.postAtTime(mAmbientLuxUpdater, timeWhenSensorWarmedUp);
- return;
- }
- mAmbientLuxValid = true;
- setAmbientLux(calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort));
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Initializing: "
- + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
- + "mAmbientLux=" + mAmbientLux);
- }
- }
-
- long nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
- long nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
- // Essentially, we calculate both a slow ambient lux, to ensure there's a true long-term
- // change in lighting conditions, and a fast ambient lux to determine what the new
- // brightness situation is since the slow lux can be quite slow to converge.
- //
- // Note that both values need to be checked for sufficient change before updating the
- // proposed ambient light value since the slow value might be sufficiently far enough away
- // from the fast value to cause a recalculation while its actually just converging on
- // the fast value still.
- mSlowAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonLong);
- mFastAmbientLux = calculateAmbientLux(time, mConfig.mAmbientLightHorizonShort);
-
- if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
- && mFastAmbientLux >= mAmbientBrighteningThreshold
- && nextBrightenTransition <= time)
- || (mSlowAmbientLux <= mAmbientDarkeningThreshold
- && mFastAmbientLux <= mAmbientDarkeningThreshold
- && nextDarkenTransition <= time)) {
- mPreThresholdLux = mAmbientLux;
- setAmbientLux(mFastAmbientLux);
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: "
- + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
- + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
- + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", "
- + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
- + "mAmbientLux=" + mAmbientLux);
- }
- nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
- nextDarkenTransition = nextAmbientLightDarkeningTransition(time);
- }
- long nextTransitionTime = Math.min(nextDarkenTransition, nextBrightenTransition);
- // If one of the transitions is ready to occur, but the total weighted ambient lux doesn't
- // exceed the necessary threshold, then it's possible we'll get a transition time prior to
- // now. Rather than continually checking to see whether the weighted lux exceeds the
- // threshold, schedule an update for when we'd normally expect another light sample, which
- // should be enough time to decide whether we should actually transition to the new
- // weighted ambient lux or not.
- nextTransitionTime = nextTransitionTime > time ? nextTransitionTime
- : time + mConfig.mNormalLightSensorRate;
- if (mLoggingEnabled) {
- Slog.d(mTag, "updateAmbientLux: Scheduling ambient lux update for "
- + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
- }
- mHandler.postAtTime(mAmbientLuxUpdater, nextTransitionTime);
- }
-
- public interface LightSensorListener {
- /**
- * Called when new ambient lux value is ready
- */
- void onAmbientLuxChange(float ambientLux);
- }
-
- private static final class LightSensorHandler extends Handler {
- private LightSensorHandler(Looper looper) {
- super(looper, /* callback= */ null, /* async= */ true);
- }
- }
-
- /**
- * A ring buffer of ambient light measurements sorted by time.
- * Each entry consists of a timestamp and a lux measurement, and the overall buffer is sorted
- * from oldest to newest.
- */
- @VisibleForTesting
- static final class AmbientLightRingBuffer {
-
- private float[] mRingLux;
- private long[] mRingTime;
- private int mCapacity;
-
- // The first valid element and the next open slot.
- // Note that if mCount is zero then there are no valid elements.
- private int mStart;
- private int mEnd;
- private int mCount;
-
- private final Clock mClock;
-
- @VisibleForTesting
- AmbientLightRingBuffer(int initialCapacity, Clock clock) {
- mCapacity = initialCapacity;
- mRingLux = new float[mCapacity];
- mRingTime = new long[mCapacity];
- mClock = clock;
-
- }
-
- @VisibleForTesting
- float getLux(int index) {
- return mRingLux[offsetOf(index)];
- }
-
- @VisibleForTesting
- float[] getAllLuxValues() {
- float[] values = new float[mCount];
- if (mCount == 0) {
- return values;
- }
-
- if (mStart < mEnd) {
- System.arraycopy(mRingLux, mStart, values, 0, mCount);
- } else {
- System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
- System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
- }
-
- return values;
- }
-
- @VisibleForTesting
- long getTime(int index) {
- return mRingTime[offsetOf(index)];
- }
-
- @VisibleForTesting
- long[] getAllTimestamps() {
- long[] values = new long[mCount];
- if (mCount == 0) {
- return values;
- }
-
- if (mStart < mEnd) {
- System.arraycopy(mRingTime, mStart, values, 0, mCount);
- } else {
- System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
- System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
- }
-
- return values;
- }
-
- @VisibleForTesting
- void push(long time, float lux) {
- int next = mEnd;
- if (mCount == mCapacity) {
- int newSize = mCapacity * 2;
-
- float[] newRingLux = new float[newSize];
- long[] newRingTime = new long[newSize];
- int length = mCapacity - mStart;
- System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
- System.arraycopy(mRingTime, mStart, newRingTime, 0, length);
- if (mStart != 0) {
- System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
- System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
- }
- mRingLux = newRingLux;
- mRingTime = newRingTime;
-
- next = mCapacity;
- mCapacity = newSize;
- mStart = 0;
- }
- mRingTime[next] = time;
- mRingLux[next] = lux;
- mEnd = next + 1;
- if (mEnd == mCapacity) {
- mEnd = 0;
- }
- mCount++;
- }
-
- @VisibleForTesting
- void prune(long horizon) {
- if (mCount == 0) {
- return;
- }
-
- while (mCount > 1) {
- int next = mStart + 1;
- if (next >= mCapacity) {
- next -= mCapacity;
- }
- if (mRingTime[next] > horizon) {
- // Some light sensors only produce data upon a change in the ambient light
- // levels, so we need to consider the previous measurement as the ambient light
- // level for all points in time up until we receive a new measurement. Thus, we
- // always want to keep the youngest element that would be removed from the
- // buffer and just set its measurement time to the horizon time since at that
- // point it is the ambient light level, and to remove it would be to drop a
- // valid data point within our horizon.
- break;
- }
- mStart = next;
- mCount -= 1;
- }
-
- if (mRingTime[mStart] < horizon) {
- mRingTime[mStart] = horizon;
- }
- }
-
- @VisibleForTesting
- int size() {
- return mCount;
- }
-
- @VisibleForTesting
- void clear() {
- mStart = 0;
- mEnd = 0;
- mCount = 0;
- }
-
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder();
- buf.append('[');
- for (int i = 0; i < mCount; i++) {
- final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis();
- if (i != 0) {
- buf.append(", ");
- }
- buf.append(getLux(i));
- buf.append(" / ");
- buf.append(next - getTime(i));
- buf.append("ms");
- }
- buf.append(']');
- return buf.toString();
- }
-
- private int offsetOf(int index) {
- if (index >= mCount || index < 0) {
- throw new ArrayIndexOutOfBoundsException(index);
- }
- index += mStart;
- if (index >= mCapacity) {
- index -= mCapacity;
- }
- return index;
- }
- }
-
- @VisibleForTesting
- interface Injector {
- Clock getClock();
-
- Sensor getLightSensor(LightSensorControllerConfig config);
-
- boolean registerLightSensorListener(
- SensorEventListener listener, Sensor sensor, int rate, Handler handler);
-
- void unregisterLightSensorListener(SensorEventListener listener);
-
- String getTag();
-
- }
-
- private static class RealInjector implements Injector {
- private final SensorManager mSensorManager;
- private final int mSensorFallbackType;
-
- private final String mTag;
-
- private RealInjector(SensorManager sensorManager, int displayId) {
- mSensorManager = sensorManager;
- mSensorFallbackType = displayId == Display.DEFAULT_DISPLAY
- ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
- mTag = "LightSensorController [" + displayId + "]";
- }
-
- @Override
- public Clock getClock() {
- return Clock.SYSTEM_CLOCK;
- }
-
- @Override
- public Sensor getLightSensor(LightSensorControllerConfig config) {
- return SensorUtils.findSensor(
- mSensorManager, config.mAmbientLightSensor, mSensorFallbackType);
- }
-
- @Override
- public boolean registerLightSensorListener(
- SensorEventListener listener, Sensor sensor, int rate, Handler handler) {
- return mSensorManager.registerListener(listener, sensor, rate * 1000, handler);
- }
-
- @Override
- public void unregisterLightSensorListener(SensorEventListener listener) {
- mSensorManager.unregisterListener(listener);
- }
-
- @Override
- public String getTag() {
- return mTag;
- }
- }
-
- public static class LightSensorControllerConfig {
- // Steady-state light sensor event rate in milliseconds.
- private final int mNormalLightSensorRate;
- private final int mInitialLightSensorRate;
-
- // If true immediately after the screen is turned on the controller will try to adjust the
- // brightness based on the current sensor reads. If false, the controller will collect
- // more data
- // and only then decide whether to change brightness.
- private final boolean mResetAmbientLuxAfterWarmUpConfig;
-
- // Period of time in which to consider light samples for a short/long-term estimate of
- // ambient
- // light in milliseconds.
- private final int mAmbientLightHorizonShort;
- private final int mAmbientLightHorizonLong;
-
-
- // Amount of time to delay auto-brightness after screen on while waiting for
- // the light sensor to warm-up in milliseconds.
- // May be 0 if no warm-up is required.
- private final int mLightSensorWarmUpTimeConfig;
-
-
- // The intercept used for the weighting calculation. This is used in order to keep all
- // possible
- // weighting values positive.
- private final int mWeightingIntercept;
-
- // Configuration object for determining thresholds to change brightness dynamically
- private final HysteresisLevels mAmbientBrightnessThresholds;
- private final HysteresisLevels mAmbientBrightnessThresholdsIdle;
-
-
- // Stability requirements in milliseconds for accepting a new brightness level. This is
- // used
- // for debouncing the light sensor. Different constants are used to debounce the light
- // sensor
- // when adapting to brighter or darker environments. This parameter controls how quickly
- // brightness changes occur in response to an observed change in light level that exceeds
- // the
- // hysteresis threshold.
- private final long mBrighteningLightDebounceConfig;
- private final long mDarkeningLightDebounceConfig;
- private final long mBrighteningLightDebounceConfigIdle;
- private final long mDarkeningLightDebounceConfigIdle;
-
- private final SensorData mAmbientLightSensor;
-
- @VisibleForTesting
- LightSensorControllerConfig(int initialLightSensorRate, int normalLightSensorRate,
- boolean resetAmbientLuxAfterWarmUpConfig, int ambientLightHorizonShort,
- int ambientLightHorizonLong, int lightSensorWarmUpTimeConfig,
- int weightingIntercept, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels ambientBrightnessThresholdsIdle,
- long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
- long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
- SensorData ambientLightSensor) {
- mInitialLightSensorRate = initialLightSensorRate;
- mNormalLightSensorRate = normalLightSensorRate;
- mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
- mAmbientLightHorizonShort = ambientLightHorizonShort;
- mAmbientLightHorizonLong = ambientLightHorizonLong;
- mLightSensorWarmUpTimeConfig = lightSensorWarmUpTimeConfig;
- mWeightingIntercept = weightingIntercept;
- mAmbientBrightnessThresholds = ambientBrightnessThresholds;
- mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle;
- mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
- mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
- mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle;
- mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle;
- mAmbientLightSensor = ambientLightSensor;
- }
-
- private void dump(PrintWriter pw) {
- pw.println("LightSensorControllerConfig:");
- pw.println(" mInitialLightSensorRate=" + mInitialLightSensorRate);
- pw.println(" mNormalLightSensorRate=" + mNormalLightSensorRate);
- pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
- pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
- pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
- pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
- pw.println(" mWeightingIntercept=" + mWeightingIntercept);
- pw.println(" mAmbientBrightnessThresholds=");
- mAmbientBrightnessThresholds.dump(pw);
- pw.println(" mAmbientBrightnessThresholdsIdle=");
- mAmbientBrightnessThresholdsIdle.dump(pw);
- pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
- pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
- pw.println(
- " mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
- pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
- pw.println(" mAmbientLightSensor=" + mAmbientLightSensor);
- }
-
- /**
- * Creates LightSensorControllerConfig object form Resources and DisplayDeviceConfig
- */
- public static LightSensorControllerConfig create(Resources res, DisplayDeviceConfig ddc) {
- int lightSensorRate = res.getInteger(R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = res.getInteger(
- R.integer.config_autoBrightnessInitialLightSensorRate);
- if (initialLightSensorRate == -1) {
- initialLightSensorRate = lightSensorRate;
- } else if (initialLightSensorRate > lightSensorRate) {
- Slog.w("LightSensorControllerConfig",
- "Expected config_autoBrightnessInitialLightSensorRate ("
- + initialLightSensorRate + ") to be less than or equal to "
- + "config_autoBrightnessLightSensorRate (" + lightSensorRate
- + ").");
- }
-
- boolean resetAmbientLuxAfterWarmUp = res.getBoolean(
- R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = res.getInteger(
- R.integer.config_lightSensorWarmupTime);
-
- return new LightSensorControllerConfig(initialLightSensorRate, lightSensorRate,
- resetAmbientLuxAfterWarmUp, ddc.getAmbientHorizonShort(),
- ddc.getAmbientHorizonLong(), lightSensorWarmUpTimeConfig,
- ddc.getAmbientHorizonLong(),
- HysteresisLevels.getAmbientBrightnessThresholds(ddc),
- HysteresisLevels.getAmbientBrightnessThresholdsIdle(ddc),
- ddc.getAutoBrightnessBrighteningLightDebounce(),
- ddc.getAutoBrightnessDarkeningLightDebounce(),
- ddc.getAutoBrightnessBrighteningLightDebounceIdle(),
- ddc.getAutoBrightnessDarkeningLightDebounceIdle(),
- ddc.getAmbientLightSensor()
- );
- }
- }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 9c7504d..a46975fb 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -285,7 +285,7 @@
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
- if (flags.isEvenDimmerEnabled()) {
+ if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) {
modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
displayDeviceConfig));
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 572d32e..d084d1c 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1142,7 +1142,11 @@
maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
}
- mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
+ // TODO(b/310237068): Make this work for multiple displays
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate,
+ maxRefreshRate);
+ }
}
private void removeRefreshRateSetting(int displayId) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 85ab773..1c14fc1 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -165,6 +165,15 @@
}
}
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void finishTrackingPendingImeVisibilityRequests() {
+ super.finishTrackingPendingImeVisibilityRequests_enforcePermission();
+ synchronized (mLock) {
+ mHistory.mLiveEntries.clear();
+ }
+ }
+
/**
* A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
*/
diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
index 8ddcf76..b881ef6 100644
--- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
+++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
@@ -134,10 +134,8 @@
@Nullable
public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
- // TODO: b/305199571 - Optimize the following statement to avoid creating the full
- // MediaRoute2Info instance. We just need the id.
return bluetoothDevice != null
- ? createBluetoothRoute(bluetoothDevice).mRoute.getId()
+ ? getRouteIdForType(bluetoothDevice, getDeviceType(bluetoothDevice))
: null;
}
@@ -227,25 +225,10 @@
newBtRoute.mBtDevice = device;
String deviceName = getDeviceName(device);
- String routeId = device.getAddress();
- int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
- newBtRoute.mConnectedProfiles = new SparseBooleanArray();
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.A2DP, true);
- }
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
- routeId = HEARING_AID_ROUTE_ID_PREFIX
- + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device);
- type = MediaRoute2Info.TYPE_HEARING_AID;
- }
- if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
- newBtRoute.mConnectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
- routeId = LE_AUDIO_ROUTE_ID_PREFIX
- + mBluetoothProfileMonitor.getGroupId(BluetoothProfile.LE_AUDIO, device);
- type = MediaRoute2Info.TYPE_BLE_HEADSET;
- }
+ int type = getDeviceType(device);
+ String routeId = getRouteIdForType(device, type);
+ newBtRoute.mConnectedProfiles = getConnectedProfiles(device);
// Note that volume is only relevant for active bluetooth routes, and those are managed via
// AudioManager.
newBtRoute.mRoute =
@@ -273,6 +256,47 @@
}
return deviceName;
}
+ private SparseBooleanArray getConnectedProfiles(@NonNull BluetoothDevice device) {
+ SparseBooleanArray connectedProfiles = new SparseBooleanArray();
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
+ connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
+ connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ }
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
+ connectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+ }
+
+ return connectedProfiles;
+ }
+
+ private int getDeviceType(@NonNull BluetoothDevice device) {
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.LE_AUDIO, device)) {
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ }
+
+ if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ }
+
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ }
+
+ private String getRouteIdForType(@NonNull BluetoothDevice device, int type) {
+ return switch (type) {
+ case (MediaRoute2Info.TYPE_BLE_HEADSET) ->
+ LE_AUDIO_ROUTE_ID_PREFIX
+ + mBluetoothProfileMonitor.getGroupId(
+ BluetoothProfile.LE_AUDIO, device);
+ case (MediaRoute2Info.TYPE_HEARING_AID) ->
+ HEARING_AID_ROUTE_ID_PREFIX
+ + mBluetoothProfileMonitor.getGroupId(
+ BluetoothProfile.HEARING_AID, device);
+ // TYPE_BLUETOOTH_A2DP
+ default -> device.getAddress();
+ };
+ }
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 5563cae..96f32f3 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -514,12 +514,16 @@
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
getPolitenessState(record));
}
- record.setAudiblyAlerted(buzz || beep);
if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
}
+
+ record.setAudiblyAlerted((buzz || beep)
+ && getPolitenessState(record) != PolitenessStrategy.POLITE_STATE_MUTED);
+ } else {
+ record.setAudiblyAlerted(buzz || beep);
}
return buzzBeepBlinkLoggingCode;
}
@@ -678,7 +682,7 @@
// The user can choose to apply cooldown for all apps/conversations only from the
// Settings app
- if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) {
+ if (!mNotificationCooldownApplyToAll && !record.isConversation()) {
return false;
}
@@ -1203,7 +1207,7 @@
setLastNotificationUpdateTimeMs(record, 0);
}
- public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
+ public @PolitenessState int getPolitenessState(final NotificationRecord record) {
return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
}
@@ -1364,7 +1368,12 @@
final String key = getChannelKey(record);
@PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ @PolitenessState int nextState;
+ if (Flags.politeNotificationsAttnUpdate()) {
+ nextState = getNextState(currState, timeSinceLastNotif, record);
+ } else {
+ nextState = getNextState(currState, timeSinceLastNotif);
+ }
if (DEBUG) {
Log.i(TAG,
@@ -1379,6 +1388,26 @@
mAppStrategy.onNotificationPosted(record);
}
+ @PolitenessState int getNextState(@PolitenessState final int currState,
+ final long timeSinceLastNotif, final NotificationRecord record) {
+ // Mute all except priority conversations
+ if (!isAvalancheExempted(record)) {
+ return POLITE_STATE_MUTED;
+ }
+ if (isAvalancheExemptedFullVolume(record)) {
+ return POLITE_STATE_DEFAULT;
+ }
+ return getNextState(currState, timeSinceLastNotif);
+ }
+
+ public @PolitenessState int getPolitenessState(final NotificationRecord record) {
+ if (isAvalancheActive()) {
+ return super.getPolitenessState(record);
+ } else {
+ return mAppStrategy.getPolitenessState(record);
+ }
+ }
+
@Override
public float getSoundVolume(final NotificationRecord record) {
if (isAvalancheActive()) {
@@ -1396,13 +1425,27 @@
@Override
String getChannelKey(final NotificationRecord record) {
- // If the user explicitly changed the channel notification sound:
- // handle as a separate channel
- if (record.getChannel().hasUserSetSound()) {
- return super.getChannelKey(record);
+ if (isAvalancheActive()) {
+ if (Flags.politeNotificationsAttnUpdate()) {
+ // Treat high importance conversations independently
+ if (isAvalancheExempted(record)) {
+ return super.getChannelKey(record);
+ } else {
+ // Use one global key per user
+ return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
+ }
+ } else {
+ // If the user explicitly changed the channel notification sound:
+ // handle as a separate channel
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getChannelKey(record);
+ } else {
+ // Use one global key per user
+ return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
+ }
+ }
} else {
- // Use one global key per user
- return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
+ return mAppStrategy.getChannelKey(record);
}
}
@@ -1415,10 +1458,19 @@
}
long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
- if (record.getChannel().hasUserSetSound()) {
- return super.getLastNotificationUpdateTimeMs(record);
+ if (Flags.politeNotificationsAttnUpdate()) {
+ // Mute all except priority conversations
+ if (isAvalancheExempted(record)) {
+ return super.getLastNotificationUpdateTimeMs(record);
+ } else {
+ return mLastNotificationTimestamp;
+ }
} else {
- return mLastNotificationTimestamp;
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getLastNotificationUpdateTimeMs(record);
+ } else {
+ return mLastNotificationTimestamp;
+ }
}
}
@@ -1445,6 +1497,51 @@
void setTriggerTimeMs(long timestamp) {
mLastAvalancheTriggerTimestamp = timestamp;
}
+
+ private boolean isAvalancheExemptedFullVolume(final NotificationRecord record) {
+ // important conversation
+ if (record.isConversation()
+ && (record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
+ || record.getChannel().isImportantConversation())) {
+ return true;
+ }
+
+ // call notification
+ if (record.getNotification().isStyle(Notification.CallStyle.class)) {
+ return true;
+ }
+
+ // alarm/reminder
+ final String category = record.getNotification().category;
+ if (Notification.CATEGORY_REMINDER.equals(category)
+ || Notification.CATEGORY_EVENT.equals(category)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isAvalancheExempted(final NotificationRecord record) {
+ if (isAvalancheExemptedFullVolume(record)) {
+ return true;
+ }
+
+ // recent conversation
+ if (record.isConversation()
+ && record.getNotification().when > mLastAvalancheTriggerTimestamp) {
+ return true;
+ }
+
+ if (record.getNotification().fullScreenIntent != null) {
+ return true;
+ }
+
+ if (record.getNotification().isColorized()) {
+ return true;
+ }
+
+ return false;
+ }
}
//====================== Observers =============================
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f48f66f..493c4a0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1794,7 +1794,7 @@
if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
// update system notification channels
SystemNotificationChannels.createAll(context);
- mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid());
+ mZenModeHelper.updateZenRulesOnLocaleChange();
mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 289faf4..20b7fd4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -86,6 +86,7 @@
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.SystemZenRules;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
@@ -214,7 +215,7 @@
mNotificationManager = context.getSystemService(NotificationManager.class);
mDefaultConfig = readDefaultConfig(mContext.getResources());
- updateDefaultAutomaticRuleNames();
+ updateDefaultConfigAutomaticRules();
if (Flags.modesApi()) {
updateDefaultAutomaticRulePolicies();
}
@@ -1020,27 +1021,41 @@
}
}
- protected void updateDefaultZenRules(int callingUid) {
- updateDefaultAutomaticRuleNames();
+ void updateZenRulesOnLocaleChange() {
+ updateDefaultConfigAutomaticRules();
synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return;
+ }
+ ZenModeConfig config = mConfig.copy();
+ boolean updated = false;
for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
- ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
- // if default rule wasn't user-modified nor enabled, use localized name
+ ZenRule currRule = config.automaticRules.get(defaultRule.id);
+ // if default rule wasn't user-modified use localized name
// instead of previous system name
- if (currRule != null && !currRule.modified && !currRule.enabled
+ if (currRule != null
+ && !currRule.modified
+ && (currRule.zenPolicyUserModifiedFields & AutomaticZenRule.FIELD_NAME) == 0
&& !defaultRule.name.equals(currRule.name)) {
- if (canManageAutomaticZenRule(currRule)) {
- if (DEBUG) {
- Slog.d(TAG, "Locale change - updating default zen rule name "
- + "from " + currRule.name + " to " + defaultRule.name);
- }
- // update default rule (if locale changed, name of rule will change)
- currRule.name = defaultRule.name;
- updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid);
+ if (DEBUG) {
+ Slog.d(TAG, "Locale change - updating default zen rule name "
+ + "from " + currRule.name + " to " + defaultRule.name);
+ }
+ currRule.name = defaultRule.name;
+ updated = true;
+ }
+ }
+ if (Flags.modesApi() && Flags.modesUi()) {
+ for (ZenRule rule : config.automaticRules.values()) {
+ if (SystemZenRules.isSystemOwnedRule(rule)) {
+ updated |= SystemZenRules.updateTriggerDescription(mContext, rule);
}
}
}
+ if (updated) {
+ setConfigLocked(config, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
+ }
}
}
@@ -1631,6 +1646,10 @@
reason += ", reset to default rules";
}
+ if (Flags.modesApi() && Flags.modesUi()) {
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+ }
+
// Resolve user id for settings.
userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
@@ -2054,7 +2073,7 @@
}
}
- private void updateDefaultAutomaticRuleNames() {
+ private void updateDefaultConfigAutomaticRules() {
for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
rule.name = mContext.getResources()
@@ -2063,6 +2082,9 @@
rule.name = mContext.getResources()
.getString(R.string.zen_mode_default_every_night_name);
}
+ if (Flags.modesApi() && Flags.modesUi()) {
+ SystemZenRules.updateTriggerDescription(mContext, rule);
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 43361ed..afd00af 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -78,4 +78,11 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "polite_notifications_attn_update"
+ namespace: "systemui"
+ description: "This flag controls the polite notification attention behavior updates as per UXR feedback"
+ bug: "270456865"
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 953300a..d39debb 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -372,6 +372,7 @@
public void onConnected(
@NonNull IOnDeviceIntelligenceService service) {
try {
+ service.ready();
service.registerRemoteServices(
getRemoteProcessingService());
} catch (RemoteException ex) {
@@ -538,6 +539,8 @@
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
synchronized (mLock) {
mTemporaryServiceNames = componentNames;
+ mRemoteInferenceService.unbind();
+ mRemoteOnDeviceIntelligenceService.unbind();
mRemoteOnDeviceIntelligenceService = null;
mRemoteInferenceService = null;
if (mTemporaryHandler == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 095a233..4da280b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2446,7 +2446,7 @@
ComponentName intentFilterVerifierComponent =
getIntentFilterVerifierComponentNameLPr(computer);
ComponentName domainVerificationAgent =
- getDomainVerificationAgentComponentNameLPr(computer);
+ getDomainVerificationAgentComponentNameLPr(computer, UserHandle.USER_SYSTEM);
DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
intentFilterVerifierComponent, domainVerificationAgent, mContext,
@@ -2754,12 +2754,13 @@
}
@Nullable
- private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer) {
+ private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer,
+ int userId) {
Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
List<ResolveInfo> matches =
mResolveIntentHelper.queryIntentReceiversInternal(computer, intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM, Binder.getCallingUid());
+ userId, Binder.getCallingUid());
ResolveInfo best = null;
final int N = matches.size();
for (int i = 0; i < N; i++) {
@@ -2767,7 +2768,7 @@
final String packageName = cur.getComponentInfo().packageName;
if (checkPermission(
android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, packageName,
- UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) {
+ userId) != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Domain verification agent found but does not hold permission: "
+ packageName);
continue;
@@ -2775,7 +2776,7 @@
if (best == null || cur.priority > best.priority) {
if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(),
- UserHandle.SYSTEM)) {
+ UserHandle.of(userId))) {
best = cur;
} else {
Slog.w(TAG, "Domain verification agent found but not enabled");
@@ -6272,9 +6273,13 @@
final int[] userIds = resolveUserIds(UserHandle.USER_ALL);
final String reason = "The mimeGroup is changed";
for (int i = 0; i < userIds.length; i++) {
- final int packageUid = UserHandle.getUid(userIds[i], appId);
- mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
- true /* dontKillApp */, components, packageUid, reason);
+ final PackageUserStateInternal pkgUserState =
+ packageState.getUserStates().get(userIds[i]);
+ if (pkgUserState != null && pkgUserState.isInstalled()) {
+ final int packageUid = UserHandle.getUid(userIds[i], appId);
+ mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
+ true /* dontKillApp */, components, packageUid, reason);
+ }
}
});
}
@@ -6508,13 +6513,13 @@
@Override
@Nullable
- public ComponentName getDomainVerificationAgent() {
+ public ComponentName getDomainVerificationAgent(int userId) {
final int callerUid = Binder.getCallingUid();
if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
throw new SecurityException("Not allowed to query domain verification agent");
}
final Computer snapshot = snapshotComputer();
- return getDomainVerificationAgentComponentNameLPr(snapshot);
+ return getDomainVerificationAgentComponentNameLPr(snapshot, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a9e1725..59faf24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4412,8 +4412,31 @@
private int runGetDomainVerificationAgent() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_ALL;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_SYSTEM, "runGetDomainVerificationAgent");
try {
- final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent();
+ final ComponentName domainVerificationAgent =
+ mInterface.getDomainVerificationAgent(translatedUserId);
pw.println(domainVerificationAgent == null
? "No Domain Verifier available!" : domainVerificationAgent.flattenToString());
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f5ed8d4..9f2c36a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -93,6 +93,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
import com.android.internal.pm.pkg.component.ParsedComponent;
import com.android.internal.pm.pkg.component.ParsedIntentInfo;
import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -911,8 +912,10 @@
sharedUserSetting.mDisabledPackages.remove(p);
}
p.getPkgState().setUpdatedSystemApp(false);
+ final AndroidPackageInternal pkg = p.getPkg();
PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
- p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
+ p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId(),
+ pkg == null ? false : pkg.isSdkLibrary());
if (ret != null) {
ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
@@ -951,8 +954,8 @@
}
}
- PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
- int pkgPrivateFlags, @NonNull UUID domainSetId) {
+ PackageSetting addPackageLPw(String name, String realName, File codePath, int uid,
+ int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId, boolean isSdkLibrary) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.getAppId() == uid) {
@@ -964,7 +967,8 @@
}
p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
.setAppId(uid);
- if (mAppIds.registerExistingAppId(uid, p, name)) {
+ if ((uid == Process.INVALID_UID && isSdkLibrary && Flags.disallowSdkLibsToBeApps())
+ || mAppIds.registerExistingAppId(uid, p, name)) {
mPackages.put(name, p);
return p;
}
@@ -4176,7 +4180,7 @@
} else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
&& Flags.disallowSdkLibsToBeApps())) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
- appId, pkgFlags, pkgPrivateFlags, domainSetId);
+ appId, pkgFlags, pkgPrivateFlags, domainSetId, isSdkLibrary);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
+ appId + " pkg=" + packageSetting);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f5ac830..63386a9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -567,7 +567,7 @@
int autoLockPreference =
Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
- Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
getMainUserIdUnchecked());
Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
@@ -615,7 +615,7 @@
int privateSpaceAutoLockPreference =
Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
- Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
getMainUserIdUnchecked());
if (privateSpaceAutoLockPreference
!= Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
@@ -714,7 +714,7 @@
if (isAutoLockForPrivateSpaceEnabled()) {
int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
- Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
getMainUserIdUnchecked());
boolean isAutoLockOnDeviceLockSelected =
autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
@@ -1052,7 +1052,8 @@
setOrUpdateAutoLockPreferenceForPrivateProfile(
Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
- Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART,
+ mainUserId));
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 73e7485..324637c 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -717,8 +717,9 @@
break;
case UserManager.DISALLOW_RUN_IN_BACKGROUND:
if (newValue) {
- int currentUser = ActivityManager.getCurrentUser();
- if (currentUser != userId && userId != UserHandle.USER_SYSTEM) {
+ final ActivityManager am = context.getSystemService(ActivityManager.class);
+ if (!am.isProfileForeground(UserHandle.of(userId))
+ && userId != UserHandle.USER_SYSTEM) {
try {
ActivityManager.getService().stopUser(userId, false, null);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9a9c4f2..77bdc45 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -458,9 +458,6 @@
mDisplayPowerRequest.dozeScreenStateReason =
Display.STATE_REASON_DRAW_WAKE_LOCK;
}
- } else {
- mDisplayPowerRequest.dozeScreenStateReason =
- Display.STATE_REASON_DEFAULT_POLICY;
}
mDisplayPowerRequest.dozeScreenBrightness = dozeScreenBrightness;
} else {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8e3c6ac..3a84897 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -3844,6 +3844,7 @@
public abstract T instantiateObject();
}
+ @SuppressWarnings("ParcelableCreator")
public static class ControllerActivityCounterImpl extends ControllerActivityCounter
implements Parcelable {
private final Clock mClock;
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 881583a..f28a91c 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -125,6 +125,11 @@
@GuardedBy("mLock")
private final Map<UidProcState, MobileDataStats> mUidStats;
+ // No reason to keep more dimensions than 3000. The 3000 is the hard top for the statsd metrics
+ // dimensions guardrail. It also will keep the result binder transaction size capped to
+ // approximately 220kB for 3000 atoms
+ private static final int UID_STATS_MAX_SIZE = 3000;
+
private final SparseIntArray mUidPreviousState;
private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
@@ -188,14 +193,18 @@
}
final UidProcState statsKey = new UidProcState(uid, previousState);
- MobileDataStats stats;
if (mUidStats.containsKey(statsKey)) {
- stats = mUidStats.get(statsKey);
- } else {
- stats = new MobileDataStats();
- mUidStats.put(statsKey, stats);
+ return mUidStats.get(statsKey);
}
- return stats;
+ if (mUidStats.size() < UID_STATS_MAX_SIZE) {
+ MobileDataStats stats = new MobileDataStats();
+ mUidStats.put(statsKey, stats);
+ return stats;
+ }
+ if (DEBUG) {
+ Slog.w(TAG, "getUidStatsForPreviousStateLocked() UID_STATS_MAX_SIZE reached");
+ }
+ return null;
}
private void noteUidProcessStateImpl(int uid, int state) {
@@ -252,10 +261,12 @@
continue;
}
MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
- stats.addTxBytes(entry.getTxBytes());
- stats.addRxBytes(entry.getRxBytes());
- stats.addTxPackets(entry.getTxPackets());
- stats.addRxPackets(entry.getRxPackets());
+ if (stats != null) {
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index 5fd787a..b9c9b64 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -29,6 +29,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.os.Binder;
import android.os.IVold;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -246,17 +247,18 @@
* Call {@link #onVolumeRemove} to remove the connection without waiting for exit
*/
public void onVolumeUnmount(VolumeInfo vol) {
- StorageUserConnection connection = onVolumeRemove(vol);
-
- Slog.i(TAG, "On volume unmount " + vol);
- if (connection != null) {
- String sessionId = vol.getId();
-
- try {
- connection.removeSessionAndWait(sessionId);
- } catch (ExternalStorageServiceException e) {
- Slog.e(TAG, "Failed to end session for vol with id: " + sessionId, e);
+ String sessionId = vol.getId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ StorageUserConnection connection = onVolumeRemove(vol);
+ Slog.i(TAG, "On volume unmount " + vol);
+ if (connection != null) {
+ connection.removeSessionAndWait(sessionId);
}
+ } catch (ExternalStorageServiceException e) {
+ Slog.e(TAG, "Failed to end session for vol with id: " + sessionId, e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1da9f25..6f16d2c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -463,7 +463,7 @@
}
void drawMagnifiedRegionBorderIfNeeded(int displayId) {
- if (Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
return;
}
@@ -653,7 +653,7 @@
mDisplayContent = displayContent;
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
- mMagnifiedViewport = Flags.magnificationAlwaysDrawFullscreenBorder()
+ mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
? null : new MagnifiedViewport();
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(mService);
@@ -697,7 +697,7 @@
mMagnificationSpec.clear();
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setShowMagnifiedBorderIfNeeded();
}
}
@@ -708,7 +708,7 @@
FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
mIsFullscreenMagnificationActivated = activated;
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setMagnifiedRegionBorderShown(activated, true);
mMagnifiedViewport.showMagnificationBoundsIfNeeded();
}
@@ -746,7 +746,7 @@
}
recomputeBounds();
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.onDisplaySizeChanged();
}
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
@@ -908,7 +908,7 @@
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.destroyWindow();
}
}
@@ -919,7 +919,7 @@
FLAGS_MAGNIFICATION_CALLBACK);
}
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.drawWindowIfNeeded();
}
}
@@ -1039,14 +1039,14 @@
}
visibleWindows.clear();
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
}
final boolean magnifiedChanged =
!mOldMagnificationRegion.equals(mMagnificationRegion);
if (magnifiedChanged) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
}
mOldMagnificationRegion.set(mMagnificationRegion);
@@ -1129,7 +1129,7 @@
}
void dump(PrintWriter pw, String prefix) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.dump(pw, prefix);
}
}
@@ -1235,7 +1235,7 @@
}
// TODO(291891390): Remove this class when we clean up the flag
- // magnificationAlwaysDrawFullscreenBorder
+ // alwaysDrawMagnificationFullscreenBorder
private final class ViewportWindow implements Runnable {
private static final String SURFACE_TITLE = "Magnification Overlay";
@@ -1540,7 +1540,7 @@
public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
// TODO(291891390): Remove this field when we clean up the flag
- // magnificationAlwaysDrawFullscreenBorder
+ // alwaysDrawMagnificationFullscreenBorder
public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
@@ -1569,7 +1569,7 @@
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mService.mGlobalLock) {
if (isFullscreenMagnificationActivated()) {
- if (!Flags.magnificationAlwaysDrawFullscreenBorder()) {
+ if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
mMagnifiedViewport.setMagnifiedRegionBorderShown(true, true);
}
mService.scheduleAnimationLocked();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17e6996..2ec26ca 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6501,9 +6501,7 @@
}
newIntents = null;
- if (isActivityTypeHome()) {
- mTaskSupervisor.updateHomeProcess(task.getBottomMostActivity().app);
- }
+ mTaskSupervisor.updateHomeProcessIfNeeded(this);
if (nowVisible) {
mTaskSupervisor.stopWaitingForActivityVisible(this);
@@ -8507,8 +8505,9 @@
applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
- final boolean isFixedOrientationLetterboxAllowed =
- parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ // Bubble activities should always fill their parent and should not be letterboxed.
+ final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
+ && (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
// When starting to switch between PiP and fullscreen, the task is pinned
// and the activity is fullscreen. But only allow to apply letterbox if the
@@ -8516,7 +8515,7 @@
|| (!mWaitForEnteringPinnedMode
&& parentWindowingMode == WINDOWING_MODE_PINNED
&& resolvedConfig.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN);
+ == WINDOWING_MODE_FULLSCREEN));
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6af496f..354cab3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2160,19 +2160,6 @@
return rect;
}
- @Override
- public ActivityManager.TaskDescription getTaskDescription(int id) {
- synchronized (mGlobalLock) {
- enforceTaskPermission("getTaskDescription()");
- final Task tr = mRootWindowContainer.anyTaskForId(id,
- MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
- if (tr != null) {
- return tr.getTaskDescription();
- }
- }
- return null;
- }
-
/**
* Sets the locusId for a particular activity.
*
@@ -3072,8 +3059,33 @@
@Override
public Bitmap getTaskDescriptionIcon(String filePath, int userId) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, "getTaskDescriptionIcon");
+ final int callingUid = Binder.getCallingUid();
+ // Verify that the caller can make the request for the given userId
+ userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ "getTaskDescriptionIcon");
+ synchronized (mGlobalLock) {
+ // Verify that the caller can make the request for given icon file path
+ final ActivityRecord matchingActivity = mRootWindowContainer.getActivity(
+ r -> {
+ if (r.taskDescription == null
+ || r.taskDescription.getIconFilename() == null) {
+ return false;
+ }
+ return r.taskDescription.getIconFilename().equals(filePath);
+ });
+ if (matchingActivity == null || (matchingActivity.getUid() != callingUid)) {
+ // Caller UID doesn't match the requested Activity's UID, check if caller is
+ // privileged
+ try {
+ enforceActivityTaskPermission("getTaskDescriptionIcon");
+ } catch (SecurityException e) {
+ Slog.w(TAG, "getTaskDescriptionIcon(): request (callingUid=" + callingUid
+ + ", filePath=" + filePath + ", user=" + userId + ") doesn't match any "
+ + "activity");
+ throw e;
+ }
+ }
+ }
final File passedIconFile = new File(filePath);
final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId),
@@ -3303,6 +3315,13 @@
return false;
}
+ /**
+ * An instance method that's easier for mocking in tests.
+ */
+ void enforceActivityTaskPermission(String func) {
+ enforceTaskPermission(func);
+ }
+
static void enforceTaskPermission(String func) {
if (checkCallingPermission(MANAGE_ACTIVITY_TASKS) == PackageManager.PERMISSION_GRANTED) {
return;
@@ -5060,6 +5079,9 @@
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_MANAGER_SLEEP_STATE_CHANGED,
FrameworkStatsLog.ACTIVITY_MANAGER_SLEEP_STATE_CHANGED__STATE__AWAKE);
startTimeTrackingFocusedActivityLocked();
+ if (mTopApp != null) {
+ mTopApp.addToPendingTop();
+ }
mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
Slog.d(TAG, "Top Process State changed to PROCESS_STATE_TOP");
mTaskSupervisor.comeOutOfSleepIfNeededLocked();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 07f52574..430232c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -902,10 +902,7 @@
+ " andResume=" + andResume);
EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r),
task.mTaskId, r.shortComponentName);
- if (r.isActivityTypeHome()) {
- // Home process is the root process of the task.
- updateHomeProcess(task.getBottomMostActivity().app);
- }
+ updateHomeProcessIfNeeded(r);
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
mService.getAppWarningsLocked().onStartActivity(r);
@@ -1050,6 +1047,16 @@
return true;
}
+ void updateHomeProcessIfNeeded(@NonNull ActivityRecord r) {
+ if (!r.isActivityTypeHome()) return;
+ // Make sure that we use the bottom most activity from the same package, because the home
+ // task can also embed third-party -1 activities.
+ final ActivityRecord bottom = r.getTask().getBottomMostActivityInSamePackage();
+ if (bottom != null) {
+ updateHomeProcess(bottom.app);
+ }
+ }
+
void updateHomeProcess(WindowProcessController app) {
if (app != null && mService.mHomeProcess != app) {
scheduleStartHome("homeChanged");
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9336338..47f4a66 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -783,7 +783,7 @@
if (balShowToastsBlocked()
&& (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
// only show a toast if either caller or real caller could launch if they opted in
- showToast("BAL blocked. go/debug-bal");
+ showToast("BAL blocked. goo.gle/android-bal");
}
return statsLog(BalVerdict.BLOCK, state);
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 21326be..8c4f9ef 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -38,7 +38,6 @@
import android.view.InsetsSourceControl;
import android.view.WindowInsets;
import android.view.inputmethod.ImeTracker;
-import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -78,17 +77,22 @@
final InsetsSourceControl control = super.getControl(target);
if (control != null && target != null && target.getWindow() != null) {
final WindowState targetWin = target.getWindow();
+ final Task task = targetWin.getTask();
// If the control target changes during the app transition with the task snapshot
// starting window and the IME snapshot is visible, in case not have duplicated IME
// showing animation during transitioning, use a flag to inform IME source control to
// skip showing animation once.
- final TaskSnapshot snapshot = targetWin.getRootTask() != null
- ? targetWin.mWmService.getTaskSnapshot(targetWin.getRootTask().mTaskId,
- 0 /* userId */, false /* isLowResolution */, false /* restoreFromDisk */)
- : null;
- control.setSkipAnimationOnce(targetWin.mActivityRecord != null
- && targetWin.mActivityRecord.hasStartingWindow()
- && snapshot != null && snapshot.hasImeSurface());
+ StartingData startingData = null;
+ if (task != null) {
+ startingData = targetWin.mActivityRecord.mStartingData;
+ if (startingData == null) {
+ final WindowState startingWin = task.topStartingWindow();
+ if (startingWin != null) {
+ startingData = startingWin.mStartingData;
+ }
+ }
+ }
+ control.setSkipAnimationOnce(startingData != null && startingData.hasImeSurface());
}
return control;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b89c12b..1ce2cd8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1277,8 +1277,9 @@
final ActivityRecord top = topRunningActivity();
final ActivityRecord resumedActivity = getResumedActivity();
if (resumedActivity != null
- && (top.getTaskFragment() != this || !canBeResumed(resuming))) {
- // Pausing the resumed activity because it is occluded by other task fragment.
+ && (top == null || top.getTaskFragment() != this || !canBeResumed(resuming))) {
+ // Pausing the resumed activity because it is occluded by other task fragment, or
+ // should not be remained in resumed state.
if (startPausing(false /* uiSleeping*/, resuming, reason)) {
someActivityPaused[0]++;
}
@@ -6799,6 +6800,15 @@
}
}
+ @Nullable
+ ActivityRecord getBottomMostActivityInSamePackage() {
+ if (realActivity == null) {
+ return null;
+ }
+ return getActivity(ar -> ar.packageName.equals(
+ realActivity.getPackageName()), false /* traverseTopToBottom */);
+ }
+
/**
* Associates the decor surface with the given TF, or create one if there
* isn't one in the Task yet. The surface will be removed with the TF,
diff --git a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
index c337523..180081c 100644
--- a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
+++ b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp
@@ -32,10 +32,10 @@
namespace {
-// Max size we allow for the result from HIDIOCGRAWUNIQ (Bluetooth address or USB serial number).
-// Copied from linux/hid.h struct hid_device->uniq char array size; the ioctl implementation
-// writes at most this many bytes to the provided buffer.
-constexpr int UNIQ_SIZE_MAX = 64;
+// Max sizes we allow for results from string ioctl calls, copied from UAPI linux/uhid.h.
+// The ioctl implementation writes at most this many bytes to the provided buffer:
+constexpr int NAME_SIZE_MAX = 128; // HIDIOCGRAWNAME (device name)
+constexpr int UNIQ_SIZE_MAX = 64; // HIDIOCGRAWUNIQ (BT address or USB serial number)
} // anonymous namespace
@@ -82,6 +82,16 @@
return info.bustype;
}
+static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawName(
+ JNIEnv* env, jclass /*clazz*/, int fd) {
+ char buf[NAME_SIZE_MAX];
+ if (ioctl(fd, HIDIOCGRAWNAME(NAME_SIZE_MAX), buf) < 0) {
+ return nullptr;
+ }
+ // Local ref is not deleted because it is returned to Java
+ return env->NewStringUTF(buf);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeGetHidrawDescSize", "(I)I",
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize},
@@ -91,6 +101,8 @@
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq},
{"nativeGetHidrawBusType", "(I)I",
(void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType},
+ {"nativeGetHidrawName", "(I)Ljava/lang/String;",
+ (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawName},
};
int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f955b91..318042e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -888,6 +888,8 @@
private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
+ private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
+
/**
* For apps targeting U+
* Enable multiple admins to coexist on the same device.
@@ -9028,7 +9030,13 @@
void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
+ if (Flags.headlessDeviceOwnerDelegateSecurityLoggingBugFix()
+ && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
+ userId = mOwners.getDeviceOwnerUserId();
+ } else {
+ userId = UserHandle.USER_SYSTEM;
+ }
}
boolean inForeground = false;
ComponentName receiverComponent = null;
@@ -21514,13 +21522,26 @@
Slogf.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account.");
return;
}
- copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+
+ if (Flags.copyAccountWithRetryEnabled()) {
+ boolean copySucceeded = false;
+ int retryAttemptsLeft = RETRY_COPY_ACCOUNT_ATTEMPTS;
+ while (!copySucceeded && (retryAttemptsLeft > 0)) {
+ Slogf.i(LOG_TAG, "Copying account. Attempts left : " + retryAttemptsLeft);
+ copySucceeded =
+ copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+ retryAttemptsLeft--;
+ }
+ } else {
+ copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
+ }
if (!keepAccountMigrated) {
removeAccount(accountToMigrate, sourceUserId);
}
+
}
- private void copyAccount(
+ private boolean copyAccount(
UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate,
String callerPackage) {
final long startTime = SystemClock.elapsedRealtime();
@@ -21538,6 +21559,8 @@
DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_MS,
startTime,
callerPackage);
+ Slogf.i(LOG_TAG, "Copy account successful to " + targetUser);
+ return true;
} else {
logCopyAccountStatus(COPY_ACCOUNT_FAILED, callerPackage);
Slogf.e(LOG_TAG, "Failed to copy account to " + targetUser);
@@ -21550,6 +21573,7 @@
logCopyAccountStatus(COPY_ACCOUNT_EXCEPTION, callerPackage);
Slogf.e(LOG_TAG, "Exception copying account to " + targetUser, e);
}
+ return false;
}
private static void logCopyAccountStatus(@CopyAccountStatus int status, String callerPackage) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index dd87572..54de64e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -22,7 +22,9 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
@@ -36,6 +38,9 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.PowerManager;
@@ -47,8 +52,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.Clock;
-import com.android.server.display.brightness.LightSensorController;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.testutils.OffsettableClock;
@@ -58,6 +61,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
@@ -65,18 +69,31 @@
public class AutomaticBrightnessControllerTest {
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
private static final float BRIGHTNESS_MAX_FLOAT = 1.0f;
+ private static final int LIGHT_SENSOR_RATE = 20;
private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 2000;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
private static final float DOZE_SCALE_FACTOR = 0.54f;
+ private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
+ private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
+ private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
+ private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000;
private static final float EPSILON = 0.001f;
private OffsettableClock mClock = new OffsettableClock();
private TestLooper mTestLooper;
private Context mContext;
private AutomaticBrightnessController mController;
+ private Sensor mLightSensor;
+ @Mock SensorManager mSensorManager;
@Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
+ @Mock HysteresisLevels mAmbientBrightnessThresholds;
@Mock HysteresisLevels mScreenBrightnessThresholds;
+ @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
@Mock BrightnessRangeController mBrightnessRangeController;
@@ -84,18 +101,17 @@
BrightnessClamperController mBrightnessClamperController;
@Mock BrightnessThrottler mBrightnessThrottler;
- @Mock
- LightSensorController mLightSensorController;
-
@Before
public void setUp() throws Exception {
// Share classloader to allow package private access.
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
mContext = InstrumentationRegistry.getContext();
setupController(BrightnessMappingStrategy.INVALID_LUX,
- BrightnessMappingStrategy.INVALID_NITS);
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ false,
+ /* useHorizon= */ true);
}
@After
@@ -107,7 +123,8 @@
}
}
- private void setupController(float userLux, float userNits) {
+ private void setupController(float userLux, float userNits, boolean applyDebounce,
+ boolean useHorizon) {
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
@@ -130,22 +147,25 @@
}
@Override
- Clock createClock() {
- return new Clock() {
- @Override
- public long uptimeMillis() {
- return mClock.now();
- }
- };
+ AutomaticBrightnessController.Clock createClock() {
+ return mClock::now;
}
}, // pass in test looper instead, pass in offsettable clock
- () -> { }, mTestLooper.getLooper(),
- brightnessMappingStrategyMap, BRIGHTNESS_MIN_FLOAT,
- BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, mScreenBrightnessThresholds,
- mScreenBrightnessThresholdsIdle,
+ () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor,
+ brightnessMappingStrategyMap, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT,
+ BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
+ INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0,
+ applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0,
+ applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0,
+ applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0,
+ RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
+ mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
+ mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mBrightnessRangeController, mBrightnessThrottler,
- userLux, userNits, mLightSensorController, mBrightnessClamperController
+ useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
+ useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
+ mBrightnessClamperController
);
when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
@@ -166,15 +186,20 @@
@Test
public void testNoHysteresisAtMinBrightness() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.02f as a brightness value
float lux1 = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness1 = 0.02f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
.thenReturn(normalizedBrightness1);
@@ -185,31 +210,39 @@
.thenReturn(1.0f);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
float normalizedBrightness2 = 0.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
.thenReturn(normalizedBrightness2);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
public void testNoHysteresisAtMaxBrightness() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.98f as a brightness value
float lux1 = 100.0f;
float normalizedBrightness1 = 0.98f;
-
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
.thenReturn(normalizedBrightness1);
@@ -220,30 +253,35 @@
.thenReturn(1.1f);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 1.0f as a brightness value (brightness_max)
float lux2 = 110.0f;
float normalizedBrightness2 = 1.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
.thenReturn(normalizedBrightness2);
// Send new sensor value and verify
- listener.onAmbientLuxChange(lux2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
public void testUserAddUserDataPoint() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
@@ -260,11 +298,12 @@
public void testRecalculateSplines() throws Exception {
// Enabling the light sensor, and setting the ambient lux to 1000
int currentLux = 1000;
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
- listener.onAmbientLuxChange(currentLux);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, currentLux));
// User sets brightness to 0.5f
when(mBrightnessMappingStrategy.getBrightness(currentLux,
@@ -294,13 +333,14 @@
@Test
public void testShortTermModelTimesOut() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -314,7 +354,7 @@
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
mTestLooper.moveTimeForward(
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
@@ -333,13 +373,14 @@
@Test
public void testShortTermModelDoesntTimeOut() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */,
@@ -358,7 +399,7 @@
mTestLooper.dispatchAll();
// Sensor reads 100000 lux,
- listener.onAmbientLuxChange(678910);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910));
mController.switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
// Verify short term model is not reset.
@@ -372,13 +413,14 @@
@Test
public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -398,7 +440,7 @@
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
mTestLooper.moveTimeForward(
mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000);
mTestLooper.dispatchAll();
@@ -417,13 +459,14 @@
@Test
public void testShortTermModelNotRestoredAfterTimeout() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
/* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0,
@@ -445,7 +488,7 @@
123f, 0.5f)).thenReturn(true);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// Do not fast-forward time.
mTestLooper.dispatchAll();
@@ -463,13 +506,14 @@
@Test
public void testSwitchBetweenModesNoUserInteractions() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 123 lux,
- listener.onAmbientLuxChange(123);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123));
when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L);
when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(
PowerManager.BRIGHTNESS_INVALID_FLOAT);
@@ -485,7 +529,7 @@
BrightnessMappingStrategy.INVALID_LUX);
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// Do not fast-forward time.
mTestLooper.dispatchAll();
@@ -501,19 +545,14 @@
@Test
public void testSwitchToIdleMappingStrategy() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
- clearInvocations(mBrightnessMappingStrategy);
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Sensor reads 1000 lux,
- listener.onAmbientLuxChange(1000);
-
-
- verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt());
-
- clearInvocations(mBrightnessMappingStrategy);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000));
// User sets brightness to 100
mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */,
@@ -522,19 +561,22 @@
/* shouldResetShortTermModel= */ true);
// There should be a user data point added to the mapper.
- verify(mBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f,
+ verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
/* brightness= */ 0.5f);
- verify(mBrightnessMappingStrategy).setBrightnessConfiguration(any());
- verify(mBrightnessMappingStrategy).getBrightness(anyFloat(), any(), anyInt());
+ verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
+ verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
- clearInvocations(mBrightnessMappingStrategy);
// Now let's do the same for idle mode
mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
-
- verify(mBrightnessMappingStrategy).getMode();
- verify(mBrightnessMappingStrategy).getShortTermModelTimeout();
- verify(mBrightnessMappingStrategy).getUserBrightness();
- verify(mBrightnessMappingStrategy).getUserLux();
+ // Called once when switching,
+ // setAmbientLux() is called twice and once in updateAutoBrightness(),
+ // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are
+ // called twice each.
+ verify(mBrightnessMappingStrategy, times(8)).getMode();
+ // Called when switching.
+ verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout();
+ verify(mBrightnessMappingStrategy, times(1)).getUserBrightness();
+ verify(mBrightnessMappingStrategy, times(1)).getUserLux();
// Ensure, after switching, original BMS is not used anymore
verifyNoMoreInteractions(mBrightnessMappingStrategy);
@@ -546,25 +588,154 @@
/* shouldResetShortTermModel= */ true);
// Ensure we use the correct mapping strategy
- verify(mIdleBrightnessMappingStrategy).addUserDataPoint(/* lux= */ 1000f,
+ verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(/* lux= */ 1000f,
/* brightness= */ 0.5f);
}
@Test
+ public void testAmbientLightHorizon() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ long increment = 500;
+ // set autobrightness to low
+ // t = 0
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+ // t = 500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+
+ // t = 1000
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 2000
+ // ensure that our reading is at 0.
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // first 10000 lux sensor event reading
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 3000
+ // lux reading should still not yet be 10000.
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 3500
+ mClock.fastForward(increment);
+ // lux has been high (10000) for 1000ms.
+ // lux reading should be 10000
+ // short horizon (ambient lux) is high, long horizon is still not high
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 4000
+ // stay high
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 4500
+ Mockito.clearInvocations(mBrightnessMappingStrategy);
+ mClock.fastForward(increment);
+ // short horizon is high, long horizon is high too
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 10000));
+ verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1);
+ assertEquals(10000.0f, mController.getAmbientLux(), EPSILON);
+
+ // t = 5000
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 5500
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertTrue(mController.getAmbientLux() > 0.0f);
+ assertTrue(mController.getAmbientLux() < 10000.0f);
+
+ // t = 6000
+ mClock.fastForward(increment);
+ // ambient lux goes to 0
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
+ assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // only the values within the horizon should be kept
+ assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+ EPSILON);
+ assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+ mController.getLastSensorTimestamps());
+ }
+
+ @Test
+ public void testHysteresisLevels() {
+ float[] ambientBrighteningThresholds = {50, 100};
+ float[] ambientDarkeningThresholds = {10, 20};
+ float[] ambientThresholdLevels = {0, 500};
+ float ambientDarkeningMinChangeThreshold = 3.0f;
+ float ambientBrighteningMinChangeThreshold = 1.5f;
+ HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+ ambientDarkeningThresholds, ambientThresholdLevels, ambientThresholdLevels,
+ ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+ // test low, activate minimum change thresholds.
+ assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+ assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+ assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+ // test max
+ // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
+ assertEquals(20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON * 2);
+ assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+ // test just below threshold
+ assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+ assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+ // test at (considered above) threshold
+ assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+ assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+ }
+
+ @Test
public void testBrightnessGetsThrottled() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return max brightness at 100 lux
final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT;
final float lux = 100.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux))
+ .thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux))
+ .thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
.thenReturn(normalizedBrightness);
// Sensor reads 100 lux. We should get max brightness.
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getRawAutomaticScreenBrightness(), 0.0f);
@@ -592,6 +763,94 @@
}
@Test
+ public void testGetSensorReadings() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+ int increment = 11;
+ int lux = 5000;
+ for (int i = 0; i < 1000; i++) {
+ lux += increment;
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // Only the values within the horizon should be kept
+ assertEquals(valuesCount, sensorValues.length);
+ assertEquals(valuesCount, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = valuesCount - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ lux -= increment;
+ sensorTimestamp -= increment;
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
+ public void testGetSensorReadingsFullBuffer() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ int initialCapacity = 150;
+
+ // Choose values such that the ring buffer is pruned
+ int increment1 = 200;
+ int lux = 5000;
+ for (int i = 0; i < 20; i++) {
+ lux += increment1;
+ mClock.fastForward(increment1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+ // Choose values such that the buffer becomes full
+ int increment2 = 1;
+ for (int i = 0; i < initialCapacity - valuesCount; i++) {
+ lux += increment2;
+ mClock.fastForward(increment2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // The buffer should be full
+ assertEquals(initialCapacity, sensorValues.length);
+ assertEquals(initialCapacity, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = initialCapacity - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+ if (i >= valuesCount) {
+ lux -= increment2;
+ sensorTimestamp -= increment2;
+ } else {
+ lux -= increment1;
+ sensorTimestamp -= increment1;
+ }
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
public void testResetShortTermModelWhenConfigChanges() {
when(mBrightnessMappingStrategy.setBrightnessConfiguration(any())).thenReturn(true);
@@ -616,22 +875,179 @@
float userNits = 500;
float userBrightness = 0.3f;
when(mBrightnessMappingStrategy.getBrightnessFromNits(userNits)).thenReturn(userBrightness);
- setupController(userLux, userNits);
+ setupController(userLux, userNits, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness);
}
@Test
+ public void testBrighteningLightDebounce() throws Exception {
+ clearInvocations(mSensorManager);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1000
+ // Lux isn't steady yet
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux is steady now
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testDarkeningLightDebounce() throws Exception {
+ clearInvocations(mSensorManager);
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2000
+ // Lux isn't steady yet
+ mClock.fastForward(2000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 4500
+ // Lux is steady now
+ mClock.fastForward(2000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testBrighteningLightDebounceIdle() throws Exception {
+ clearInvocations(mSensorManager);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 500
+ // Lux isn't steady yet
+ mClock.fastForward(500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+
+ // t = 1500
+ // Lux is steady now
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
+ public void testDarkeningLightDebounceIdle() throws Exception {
+ clearInvocations(mSensorManager);
+ when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat()))
+ .thenReturn(10000f);
+ setupController(BrightnessMappingStrategy.INVALID_LUX,
+ BrightnessMappingStrategy.INVALID_NITS, /* applyDebounce= */ true,
+ /* useHorizon= */ false);
+
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_IDLE);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // t = 0
+ // Initial lux
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 1000
+ // Lux isn't steady yet
+ mClock.fastForward(1000);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(1200, mController.getAmbientLux(), EPSILON);
+
+ // t = 2500
+ // Lux is steady now
+ mClock.fastForward(1500);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
+ assertEquals(500, mController.getAmbientLux(), EPSILON);
+ }
+
+ @Test
public void testBrightnessBasedOnLastObservedLux() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
// Send a new sensor value, disable the sensor and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null,
/* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
/* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON,
@@ -643,19 +1059,21 @@
@Test
public void testAutoBrightnessInDoze() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Set policy to DOZE
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
@@ -664,7 +1082,7 @@
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should be scaled by the doze factor
assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
@@ -677,19 +1095,21 @@
@Test
public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Switch mode to DOZE
mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
@@ -701,7 +1121,7 @@
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
@@ -713,19 +1133,21 @@
@Test
public void testAutoBrightnessInDoze_ShouldNotScaleIfScreenOn() throws Exception {
- ArgumentCaptor<LightSensorController.LightSensorListener> listenerCaptor =
- ArgumentCaptor.forClass(LightSensorController.LightSensorListener.class);
- verify(mLightSensorController).setListener(listenerCaptor.capture());
- LightSensorController.LightSensorListener listener = listenerCaptor.getValue();
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
// Set up system to return 0.3f as a brightness value
float lux = 100.0f;
// Brightness as float (from 0.0f to 1.0f)
float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
/* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mLightSensorController.getLastObservedLux()).thenReturn(lux);
// Set policy to DOZE
mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
@@ -734,7 +1156,7 @@
/* shouldResetShortTermModel= */ true);
// Send a new sensor value
- listener.onAmbientLuxChange(lux);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
// The brightness should not be scaled by the doze factor
assertEquals(normalizedBrightness,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index db2a1f4..afb87d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
@@ -78,8 +79,6 @@
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
-import com.android.server.display.brightness.LightSensorController;
-import com.android.server.display.brightness.TestUtilsKt;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
@@ -1167,19 +1166,30 @@
any(AutomaticBrightnessController.Callbacks.class),
any(Looper.class),
eq(mSensorManagerMock),
+ /* lightSensor= */ any(),
/* brightnessMappingStrategyMap= */ any(SparseArray.class),
+ /* lightSensorWarmUpTime= */ anyInt(),
/* brightnessMin= */ anyFloat(),
/* brightnessMax= */ anyFloat(),
/* dozeScaleFactor */ anyFloat(),
+ /* lightSensorRate= */ anyInt(),
+ /* initialLightSensorRate= */ anyInt(),
+ /* brighteningLightDebounceConfig */ anyLong(),
+ /* darkeningLightDebounceConfig */ anyLong(),
+ /* brighteningLightDebounceConfigIdle= */ anyLong(),
+ /* darkeningLightDebounceConfigIdle= */ anyLong(),
+ /* resetAmbientLuxAfterWarmUpConfig= */ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
eq(mContext),
any(BrightnessRangeController.class),
any(BrightnessThrottler.class),
+ /* ambientLightHorizonShort= */ anyInt(),
+ /* ambientLightHorizonLong= */ anyInt(),
eq(lux),
eq(nits),
- eq(DISPLAY_ID),
- any(LightSensorController.LightSensorControllerConfig.class),
any(BrightnessClamperController.class)
);
}
@@ -2148,22 +2158,22 @@
}
@Override
- LightSensorController.LightSensorControllerConfig getLightSensorControllerConfig(
- Context context, DisplayDeviceConfig displayDeviceConfig) {
- return TestUtilsKt.createLightSensorControllerConfig();
- }
-
- @Override
AutomaticBrightnessController getAutomaticBrightnessController(
AutomaticBrightnessController.Callbacks callbacks, Looper looper,
- SensorManager sensorManager,
+ SensorManager sensorManager, Sensor lightSensor,
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap,
- float brightnessMin, float brightnessMax, float dozeScaleFactor,
+ int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+ float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+ long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+ long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle,
+ boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds,
+ HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessRangeController,
- BrightnessThrottler brightnessThrottler, float userLux, float userNits,
- int displayId, LightSensorController.LightSensorControllerConfig config,
+ BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
+ int ambientLightHorizonLong, float userLux, float userNits,
BrightnessClamperController brightnessClamperController) {
return mAutomaticBrightnessController;
}
@@ -2176,12 +2186,18 @@
}
@Override
- HysteresisLevels getBrightnessThresholdsIdleHysteresisLevels(DisplayDeviceConfig ddc) {
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold) {
return mHysteresisLevels;
}
@Override
- HysteresisLevels getBrightnessThresholdsHysteresisLevels(DisplayDeviceConfig ddc) {
+ HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+ float[] darkeningThresholdLevels, float minDarkeningThreshold,
+ float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
return mHysteresisLevels;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt b/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt
deleted file mode 100644
index 02d6946..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/HysteresisLevelsTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display
-
-import androidx.test.filters.SmallTest
-import com.android.server.display.brightness.createHysteresisLevels
-import kotlin.test.assertEquals
-import org.junit.Test
-
-private const val FLOAT_TOLERANCE = 0.001f
-@SmallTest
-class HysteresisLevelsTest {
- @Test
- fun `test hysteresis levels`() {
- val hysteresisLevels = createHysteresisLevels(
- brighteningThresholdsPercentages = floatArrayOf(50f, 100f),
- darkeningThresholdsPercentages = floatArrayOf(10f, 20f),
- brighteningThresholdLevels = floatArrayOf(0f, 500f),
- darkeningThresholdLevels = floatArrayOf(0f, 500f),
- minDarkeningThreshold = 3f,
- minBrighteningThreshold = 1.5f
- )
-
- // test low, activate minimum change thresholds.
- assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), FLOAT_TOLERANCE)
- assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), FLOAT_TOLERANCE)
- assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), FLOAT_TOLERANCE)
-
- // test max
- // epsilon is x2 here, since the next floating point value about 20,000 is 0.0019531 greater
- assertEquals(
- 20000f, hysteresisLevels.getBrighteningThreshold(10000.0f), FLOAT_TOLERANCE * 2)
- assertEquals(8000f, hysteresisLevels.getDarkeningThreshold(10000.0f), FLOAT_TOLERANCE)
-
- // test just below threshold
- assertEquals(748.5f, hysteresisLevels.getBrighteningThreshold(499f), FLOAT_TOLERANCE)
- assertEquals(449.1f, hysteresisLevels.getDarkeningThreshold(499f), FLOAT_TOLERANCE)
-
- // test at (considered above) threshold
- assertEquals(1000f, hysteresisLevels.getBrighteningThreshold(500f), FLOAT_TOLERANCE)
- assertEquals(400f, hysteresisLevels.getDarkeningThreshold(500f), FLOAT_TOLERANCE)
- }
-}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt
deleted file mode 100644
index 5fe9178..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/AmbientLightRingBufferTest.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness
-
-import androidx.test.filters.SmallTest
-import com.android.internal.os.Clock
-import com.android.server.display.brightness.LightSensorController.AmbientLightRingBuffer
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
-import org.junit.Test
-import org.mockito.kotlin.mock
-
-
-private const val BUFFER_INITIAL_CAPACITY = 3
-
-@SmallTest
-class AmbientLightRingBufferTest {
-
- private val buffer = AmbientLightRingBuffer(BUFFER_INITIAL_CAPACITY, mock<Clock>())
-
- @Test
- fun `test created empty`() {
- assertThat(buffer.size()).isEqualTo(0)
- }
-
- @Test
- fun `test push to empty buffer`() {
- buffer.push(1000, 0.5f)
-
- assertThat(buffer.size()).isEqualTo(1)
- assertThat(buffer.getLux(0)).isEqualTo(0.5f)
- assertThat(buffer.getTime(0)).isEqualTo(1000)
- }
-
- @Test
- fun `test prune keeps youngest outside horizon and sets time to horizon`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.prune(2500)
-
- assertThat(buffer.size()).isEqualTo(2)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.6f)
- assertThat(buffer.getTime(0)).isEqualTo(2500)
- }
-
- @Test
- fun `test prune keeps inside horizon`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.prune(2500)
-
- assertThat(buffer.size()).isEqualTo(2)
-
- assertThat(buffer.getLux(1)).isEqualTo(0.7f)
- assertThat(buffer.getTime(1)).isEqualTo(3000)
- }
-
-
- @Test
- fun `test pushes correctly after prune`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
- buffer.prune(2500)
-
- buffer.push(4000, 0.8f)
-
- assertThat(buffer.size()).isEqualTo(3)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.6f)
- assertThat(buffer.getTime(0)).isEqualTo(2500)
- assertThat(buffer.getLux(1)).isEqualTo(0.7f)
- assertThat(buffer.getTime(1)).isEqualTo(3000)
- assertThat(buffer.getLux(2)).isEqualTo(0.8f)
- assertThat(buffer.getTime(2)).isEqualTo(4000)
- }
-
- @Test
- fun `test increase buffer size`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.push(4000, 0.8f)
-
- assertThat(buffer.size()).isEqualTo(4)
-
- assertThat(buffer.getLux(0)).isEqualTo(0.5f)
- assertThat(buffer.getTime(0)).isEqualTo(1000)
- assertThat(buffer.getLux(1)).isEqualTo(0.6f)
- assertThat(buffer.getTime(1)).isEqualTo(2000)
- assertThat(buffer.getLux(2)).isEqualTo(0.7f)
- assertThat(buffer.getTime(2)).isEqualTo(3000)
- assertThat(buffer.getLux(3)).isEqualTo(0.8f)
- assertThat(buffer.getTime(3)).isEqualTo(4000)
- }
-
- @Test
- fun `test buffer clear`() {
- buffer.push(1000, 0.5f)
- buffer.push(2000, 0.6f)
- buffer.push(3000, 0.7f)
-
- buffer.clear()
-
- assertThat(buffer.size()).isEqualTo(0)
- assertThrows(ArrayIndexOutOfBoundsException::class.java) {
- buffer.getLux(0)
- }
- }
-}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt
deleted file mode 100644
index 966134a..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/LightSensorControllerTest.kt
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness
-
-import android.hardware.Sensor
-import android.hardware.SensorEvent
-import android.hardware.SensorEventListener
-import android.os.Handler
-import androidx.test.filters.SmallTest
-import com.android.internal.os.Clock
-import com.android.server.display.TestUtils
-import com.android.server.display.brightness.LightSensorController.Injector
-import com.android.server.display.brightness.LightSensorController.LightSensorControllerConfig
-import com.android.server.testutils.OffsettableClock
-import com.android.server.testutils.TestHandler
-import com.google.common.truth.Truth.assertThat
-import kotlin.math.ceil
-import kotlin.test.assertEquals
-import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertTrue
-import org.junit.Test
-
-private const val FLOAT_TOLERANCE = 0.001f
-
-@SmallTest
-class LightSensorControllerTest {
-
- private val testHandler = TestHandler(null)
- private val testInjector = TestInjector()
-
- @Test
- fun `test ambient light horizon`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfig = 0,
- darkeningLightDebounceConfig = 0,
- ambientLightHorizonShort = 1000,
- ambientLightHorizonLong = 2000
- ), testInjector, testHandler)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- val timeIncrement = 500L
- // set ambient lux to low
- // t = 0
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
-
- // t = 500
- //
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
-
- // t = 1000
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 1500
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 2000
- // ensure that our reading is at 0.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 2500
- // first 10000 lux sensor event reading
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 3000
- // lux reading should still not yet be 10000.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 3500
- testInjector.clock.fastForward(timeIncrement)
- // at short horizon, first value outside will be used in calculation (t = 2000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 4000
- // lux has been high (10000) for more than 1000ms.
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 4500
- testInjector.clock.fastForward(timeIncrement)
- // short horizon is high, long horizon is high too
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(10_000f))
- assertEquals(10_000f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t = 5000
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 5500
- testInjector.clock.fastForward(timeIncrement)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 6000
- testInjector.clock.fastForward(timeIncrement)
- // at short horizon, first value outside will be used in calculation (t = 4500)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertTrue(reportedAmbientLux > 0f)
- assertTrue(reportedAmbientLux < 10_000f)
-
- // t = 6500
- testInjector.clock.fastForward(timeIncrement)
- // ambient lux goes to 0
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(0f))
- assertEquals(0f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // only the values within the horizon should be kept
- assertArrayEquals(floatArrayOf(10_000f, 0f, 0f, 0f, 0f),
- lightSensorController.lastSensorValues, FLOAT_TOLERANCE)
- assertArrayEquals(longArrayOf(4_500, 5_000, 5_500, 6_000, 6_500),
- lightSensorController.lastSensorTimestamps)
- }
-
- @Test
- fun `test brightening debounce`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfig = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // brightening threshold is set to previous lux value
- ambientBrightnessThresholds = createHysteresisLevels(
- brighteningThresholdLevels = floatArrayOf(),
- brighteningThresholdsPercentages = floatArrayOf(),
- minBrighteningThreshold = 0f
- )
- ), testInjector, testHandler)
- lightSensorController.setIdleMode(false)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial brightening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux increase, first brightening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < brighteningLightDebounceConfig)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < brighteningLightDebounceConfig)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f))
- assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test sensor readings`() {
- val ambientLightHorizonLong = 2_500
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- ambientLightHorizonLong = ambientLightHorizonLong
- ), testInjector, testHandler)
- lightSensorController.setListener { }
- lightSensorController.setIdleMode(false)
- lightSensorController.enableLightSensorIfNeeded()
-
- // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
- val increment = 11
- var lux = 5000
- for (i in 0 until 1000) {
- lux += increment
- testInjector.clock.fastForward(increment.toLong())
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(lux.toFloat()))
- }
-
- val valuesCount = ceil(ambientLightHorizonLong.toDouble() / increment + 1).toInt()
- val sensorValues = lightSensorController.lastSensorValues
- val sensorTimestamps = lightSensorController.lastSensorTimestamps
-
- // Only the values within the horizon should be kept
- assertEquals(valuesCount, sensorValues.size)
- assertEquals(valuesCount, sensorTimestamps.size)
-
- var sensorTimestamp = testInjector.clock.now()
- for (i in valuesCount - 1 downTo 1) {
- assertEquals(lux.toFloat(), sensorValues[i], FLOAT_TOLERANCE)
- assertEquals(sensorTimestamp, sensorTimestamps[i])
- lux -= increment
- sensorTimestamp -= increment
- }
- assertEquals(lux.toFloat(), sensorValues[0], FLOAT_TOLERANCE)
- assertEquals(testInjector.clock.now() - ambientLightHorizonLong, sensorTimestamps[0])
- }
-
- @Test
- fun `test darkening debounce`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- darkeningLightDebounceConfig = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // darkening threshold is set to previous lux value
- ambientBrightnessThresholds = createHysteresisLevels(
- darkeningThresholdLevels = floatArrayOf(),
- darkeningThresholdsPercentages = floatArrayOf(),
- minDarkeningThreshold = 0f
- )
- ), testInjector, testHandler)
-
- lightSensorController.setIdleMode(false)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial darkening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux decreased, first darkening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < darkeningLightDebounceConfig)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < darkeningLightDebounceConfig)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f))
- assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test brightening debounce in idle mode`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- brighteningLightDebounceConfigIdle = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // brightening threshold is set to previous lux value
- ambientBrightnessThresholdsIdle = createHysteresisLevels(
- brighteningThresholdLevels = floatArrayOf(),
- brighteningThresholdsPercentages = floatArrayOf(),
- minBrighteningThreshold = 0f
- )
- ), testInjector, testHandler)
- lightSensorController.setIdleMode(true)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial brightening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux increase, first brightening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < brighteningLightDebounceConfigIdle)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2000f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < brighteningLightDebounceConfigIdle)
- // Lux increase, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(2200f))
- assertEquals(2200f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
- @Test
- fun `test darkening debounce in idle mode`() {
- val lightSensorController = LightSensorController(
- createLightSensorControllerConfig(
- lightSensorWarmUpTimeConfig = 0, // no warmUp time, can use first event
- darkeningLightDebounceConfigIdle = 1500,
- ambientLightHorizonShort = 0, // only last value will be used for lux calculation
- ambientLightHorizonLong = 10_000,
- // darkening threshold is set to previous lux value
- ambientBrightnessThresholdsIdle = createHysteresisLevels(
- darkeningThresholdLevels = floatArrayOf(),
- darkeningThresholdsPercentages = floatArrayOf(),
- minDarkeningThreshold = 0f
- )
- ), testInjector, testHandler)
-
- lightSensorController.setIdleMode(true)
-
- var reportedAmbientLux = 0f
- lightSensorController.setListener { lux ->
- reportedAmbientLux = lux
- }
- lightSensorController.enableLightSensorIfNeeded()
-
- assertThat(testInjector.sensorEventListener).isNotNull()
-
- // t0 (0)
- // Initial lux, initial darkening threshold
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(1200f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t1 (1000)
- // Lux decreased, first darkening event
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(800f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t2 (2000) (t2 - t1 < darkeningLightDebounceConfigIdle)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(500f))
- assertEquals(1200f, reportedAmbientLux, FLOAT_TOLERANCE)
-
- // t3 (3000) (t3 - t1 < darkeningLightDebounceConfigIdle)
- // Lux decreased, but isn't steady yet
- testInjector.clock.fastForward(1000)
- testInjector.sensorEventListener!!.onSensorChanged(sensorEvent(400f))
- assertEquals(400f, reportedAmbientLux, FLOAT_TOLERANCE)
- }
-
-
- private fun sensorEvent(value: Float) = SensorEvent(
- testInjector.testSensor, 0, 0, floatArrayOf(value)
- )
-
- private class TestInjector : Injector {
- val testSensor: Sensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor")
- val clock: OffsettableClock = OffsettableClock.Stopped()
-
- var sensorEventListener: SensorEventListener? = null
- override fun getClock(): Clock {
- return object : Clock() {
- override fun uptimeMillis(): Long {
- return clock.now()
- }
- }
- }
-
- override fun getLightSensor(config: LightSensorControllerConfig): Sensor {
- return testSensor
- }
-
- override fun registerLightSensorListener(
- listener: SensorEventListener,
- sensor: Sensor,
- rate: Int,
- handler: Handler
- ): Boolean {
- sensorEventListener = listener
- return true
- }
-
- override fun unregisterLightSensorListener(listener: SensorEventListener) {
- sensorEventListener = null
- }
-
- override fun getTag(): String {
- return "LightSensorControllerTest"
- }
- }
-}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt
deleted file mode 100644
index 1328f5f..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/TestUtils.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.brightness
-
-import com.android.server.display.HysteresisLevels
-import com.android.server.display.config.SensorData
-
-@JvmOverloads
-fun createLightSensorControllerConfig(
- initialSensorRate: Int = 1,
- normalSensorRate: Int = 2,
- resetAmbientLuxAfterWarmUpConfig: Boolean = true,
- ambientLightHorizonShort: Int = 1,
- ambientLightHorizonLong: Int = 10_000,
- lightSensorWarmUpTimeConfig: Int = 0,
- weightingIntercept: Int = 10_000,
- ambientBrightnessThresholds: HysteresisLevels = createHysteresisLevels(),
- ambientBrightnessThresholdsIdle: HysteresisLevels = createHysteresisLevels(),
- brighteningLightDebounceConfig: Long = 100_000,
- darkeningLightDebounceConfig: Long = 100_000,
- brighteningLightDebounceConfigIdle: Long = 100_000,
- darkeningLightDebounceConfigIdle: Long = 100_000,
- ambientLightSensor: SensorData = SensorData()
-) = LightSensorController.LightSensorControllerConfig(
- initialSensorRate,
- normalSensorRate,
- resetAmbientLuxAfterWarmUpConfig,
- ambientLightHorizonShort,
- ambientLightHorizonLong,
- lightSensorWarmUpTimeConfig,
- weightingIntercept,
- ambientBrightnessThresholds,
- ambientBrightnessThresholdsIdle,
- brighteningLightDebounceConfig,
- darkeningLightDebounceConfig,
- brighteningLightDebounceConfigIdle,
- darkeningLightDebounceConfigIdle,
- ambientLightSensor
-)
-
-fun createHysteresisLevels(
- brighteningThresholdsPercentages: FloatArray = floatArrayOf(),
- darkeningThresholdsPercentages: FloatArray = floatArrayOf(),
- brighteningThresholdLevels: FloatArray = floatArrayOf(),
- darkeningThresholdLevels: FloatArray = floatArrayOf(),
- minDarkeningThreshold: Float = 0f,
- minBrighteningThreshold: Float = 0f,
- potentialOldBrightnessRange: Boolean = false
-) = HysteresisLevels(
- brighteningThresholdsPercentages,
- darkeningThresholdsPercentages,
- brighteningThresholdLevels,
- darkeningThresholdLevels,
- minDarkeningThreshold,
- minBrighteningThreshold,
- potentialOldBrightnessRange
-)
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 3a59c84..fbc38a2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1349,6 +1349,84 @@
}
@Test
+ public void testLockFps_DisplayWithOneMode() throws Exception {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 90),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ final FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setRefreshRateInLowZone(90);
+ config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+ config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+ director.getSettingsObserver().setDefaultRefreshRate(90);
+
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
+ ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+ .registerListener(
+ sensorListenerCaptor.capture(),
+ eq(lightSensor),
+ anyInt(),
+ any(Handler.class));
+ SensorEventListener sensorListener = sensorListenerCaptor.getValue();
+
+ setBrightness(10, 10, displayListener);
+ // Sensor reads 20 lux
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, /* lux= */ 20));
+
+ Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+ assertVoteForPhysicalRefreshRate(vote, /* fps= */ 90);
+ vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+ assertThat(vote).isNotNull();
+ assertThat(vote).isInstanceOf(DisableRefreshRateSwitchingVote.class);
+ DisableRefreshRateSwitchingVote disableVote = (DisableRefreshRateSwitchingVote) vote;
+ assertThat(disableVote.mDisableRefreshRateSwitching).isTrue();
+
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(10, 125, displayListener);
+ // Sensor reads 1000 lux
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, /* lux= */ 1000));
+
+ vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+ assertThat(vote).isNull();
+ vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+ assertThat(vote).isNull();
+ }
+
+ @Test
public void testLockFpsForHighZone() throws Exception {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
index 2366f56..7aafa8e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
@@ -24,9 +24,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.os.Binder;
@@ -72,6 +74,7 @@
@Mock private WindowManagerInternal mWindowManager;
@Mock private MediaProjectionManager mProjectionManager;
+ @Mock private PackageManagerInternal mPackageManagerInternal;
private MediaProjectionInfo mMediaProjectionInfo;
@Captor
@@ -91,7 +94,7 @@
mSensitiveContentProtectionManagerService =
new SensitiveContentProtectionManagerService(mContext);
mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager,
- new ArraySet<>(Set.of(mExemptedScreenRecorderPackage)));
+ mPackageManagerInternal, new ArraySet<>(Set.of(mExemptedScreenRecorderPackage)));
verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue();
mMediaProjectionInfo =
@@ -146,6 +149,20 @@
}
@Test
+ public void testAutofillServicePackageExemption() {
+ String testAutofillService = mScreenRecorderPackage + "/com.example.SampleAutofillService";
+ int userId = Process.myUserHandle().getIdentifier();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.AUTOFILL_SERVICE, testAutofillService , userId);
+
+ mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+ verify(mWindowManager, never())
+ .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ }
+
+ @Test
public void testDeveloperOptionDisableFeature() {
mockDisabledViaDeveloperOption();
mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo);
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index e74fe29..5065144 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -104,6 +105,9 @@
private WindowManagerInternal mWindowManager;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+
+ @Mock
private StatusBarNotification mNotification1;
@Mock
@@ -141,7 +145,7 @@
setupSensitiveNotification();
mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager,
- new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE)));
+ mPackageManagerInternal, new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE)));
// Obtain useful mMediaProjectionCallback
verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 9975221..ce5cee0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -137,6 +137,8 @@
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -149,6 +151,7 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -159,6 +162,7 @@
import android.util.Log;
import android.util.SparseArray;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.MockedVoidMethod;
@@ -183,6 +187,7 @@
import libcore.util.EmptyArray;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -194,6 +199,7 @@
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -224,6 +230,7 @@
private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback;
private IAppOpsCallback mIAppOpsCallback;
private IAlarmManager mBinder;
+ private File mTestDir;
@Mock
private Context mMockContext;
@Mock
@@ -413,6 +420,7 @@
.mockStatic(PermissionManagerService.class)
.mockStatic(ServiceManager.class)
.mockStatic(SystemProperties.class)
+ .mockStatic(Environment.class)
.spyStatic(UserHandle.class)
.afterSessionFinished(
() -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
@@ -429,7 +437,8 @@
*/
private void disableFlagsNotSetByAnnotation() {
try {
- mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
+ Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
} catch (FlagSetException fse) {
// Expected if the test about to be run requires this enabled.
}
@@ -460,7 +469,10 @@
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE);
doReturn(Looper.getMainLooper()).when(Looper::myLooper);
-
+ mTestDir = new File(InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getFilesDir(), "alarmsTestDir");
+ mTestDir.mkdirs();
+ doReturn(mTestDir).when(Environment::getDataSystemDirectory);
when(mMockContext.getContentResolver()).thenReturn(mContentResolver);
doReturn(mDeviceConfigKeys).when(mDeviceConfigProperties).getKeyset();
@@ -579,6 +591,12 @@
setTestableQuotas();
}
+ @After
+ public void tearDown() {
+ // Clean up test dir to remove persisted user files.
+ FileUtils.deleteContentsAndDir(mTestDir);
+ }
+
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
setTestAlarm(type, triggerTime, operation, 0, FLAG_STANDALONE, TEST_CALLING_UID);
}
@@ -3792,6 +3810,7 @@
}
@EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void exactListenerAlarmsRemovedOnFrozen() {
mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
@@ -3823,6 +3842,7 @@
}
@EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
+ @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
@Test
public void alarmCountOnListenerFrozen() {
mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
new file mode 100644
index 0000000..5d3e499
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.alarm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.alarm.UserWakeupStore.BUFFER_TIME_MS;
+import static com.android.server.alarm.UserWakeupStore.USER_START_TIME_DEVIATION_LIMIT_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.SystemClock;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+
+@RunWith(AndroidJUnit4.class)
+public class UserWakeupStoreTest {
+ private static final int USER_ID_1 = 10;
+ private static final int USER_ID_2 = 11;
+ private static final int USER_ID_3 = 12;
+ private static final long TEST_TIMESTAMP = 150_000;
+ private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
+ .getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
+ private static final File ROOT_DIR = new File(TEST_SYSTEM_DIR, UserWakeupStore.ROOT_DIR_NAME);
+ private ExecutorService mMockExecutorService = null;
+ UserWakeupStore mUserWakeupStore;
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(Environment.class)
+ .mockStatic(BackgroundThread.class)
+ .build();
+
+ @Before
+ public void setUp() {
+ TEST_SYSTEM_DIR.mkdirs();
+ doReturn(TEST_SYSTEM_DIR).when(Environment::getDataSystemDirectory);
+ mMockExecutorService = Mockito.mock(ExecutorService.class);
+ Mockito.doAnswer((invocation) -> {
+ Runnable task = invocation.getArgument(0);
+ task.run();
+ return null;
+ }).when(mMockExecutorService).execute(Mockito.any(Runnable.class));
+ doReturn(mMockExecutorService).when(BackgroundThread::getExecutor);
+ mUserWakeupStore = new UserWakeupStore();
+ spyOn(mUserWakeupStore);
+ mUserWakeupStore.init();
+ }
+
+ @After
+ public void tearDown() {
+ // Clean up test dir to remove persisted user files.
+ FileUtils.deleteContentsAndDir(TEST_SYSTEM_DIR);
+ }
+
+ @Test
+ public void testAddWakeups() {
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+ assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ ArrayList<Integer> userIds = new ArrayList<>();
+ userIds.add(USER_ID_1);
+ userIds.add(USER_ID_2);
+ userIds.add(USER_ID_3);
+ final int[] usersToWakeup = mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP);
+ ArrayList<Integer> userWakeups = new ArrayList<>();
+ for (int i = 0; i < usersToWakeup.length; i++) {
+ userWakeups.add(usersToWakeup[i]);
+ }
+ Collections.sort(userIds);
+ Collections.sort(userWakeups);
+ assertEquals(userIds, userWakeups);
+
+ final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
+ assertTrue(file.exists());
+ }
+
+ @Test
+ public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
+ final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 7_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_1, finalAlarmTime);
+ assertEquals(1, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ final long alarmTime = mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1)
+ + BUFFER_TIME_MS;
+ assertTrue(finalAlarmTime + USER_START_TIME_DEVIATION_LIMIT_MS >= alarmTime);
+ assertTrue(finalAlarmTime - USER_START_TIME_DEVIATION_LIMIT_MS <= alarmTime);
+ }
+
+ @Test
+ public void testRemoveWakeupForUser_negativeWakeupTimeIsReturnedForUser() {
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+ assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ mUserWakeupStore.removeUserWakeup(USER_ID_3);
+ assertEquals(-1, mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) > 0);
+ }
+
+ @Test
+ public void testGetNextUserWakeup() {
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 3_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+ assertEquals(mUserWakeupStore.getNextWakeupTime(),
+ mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1));
+ mUserWakeupStore.removeUserWakeup(USER_ID_1);
+ assertEquals(mUserWakeupStore.getNextWakeupTime(),
+ mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+ }
+
+ @Test
+ public void testWriteAndReadUsersFromFile() {
+ mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 19_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_2, TEST_TIMESTAMP - 7_000);
+ mUserWakeupStore.addUserWakeup(USER_ID_3, TEST_TIMESTAMP - 13_000);
+ assertEquals(3, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ mUserWakeupStore.init();
+ final long realtime = SystemClock.elapsedRealtime();
+ assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) > realtime);
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1)
+ < mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3));
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3)
+ < mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2));
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_1) - realtime
+ < BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_3) - realtime
+ < 2 * BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+ assertTrue(mUserWakeupStore.getWakeupTimeForUserForTest(USER_ID_2) - realtime
+ < 3 * BUFFER_TIME_MS + USER_START_TIME_DEVIATION_LIMIT_MS);
+ }
+ //TODO: b/330264023 - Add tests for I/O in usersWithAlarmClocks.xml.
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 872ac40..4c7a8fe 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -479,15 +479,7 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- final int expectedAdj;
- if (sService.mConstants.ENABLE_NEW_OOMADJ) {
- // A cached empty process can be at best a level higher than the min cached adj.
- expectedAdj = sFirstCachedAdj;
- } else {
- // This is wrong but legacy behavior is going to be removed and not worth fixing.
- expectedAdj = CACHED_APP_MIN_ADJ;
- }
-
+ final int expectedAdj = sFirstCachedAdj;
assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
SCHED_GROUP_BACKGROUND);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 538c0ee..c9aab53 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -166,7 +166,7 @@
null
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable())) {
+ nullable(), nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 6aa1825..759a974 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -736,9 +736,10 @@
Mockito.clearInvocations(mKeyguardManager);
Mockito.clearInvocations(mSpiedContext);
- // Finally, set the preference to don't auto-lock
+ // Finally, set the preference to auto-lock only after device restart, which is the default
+ // behaviour
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
- Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART);
// Verify that inactivity broadcasts are unregistered and keyguard listener was removed
Mockito.verify(mSpiedContext).unregisterReceiver(any());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
index 344e2c2..69a98ac 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.accessibilityservice.BrailleDisplayController;
+import android.accessibilityservice.IBrailleDisplayController;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -174,6 +175,17 @@
}
@Test
+ public void defaultNativeScanner_getName_returnsName() {
+ String name = "My Braille Display";
+ when(mNativeInterface.getHidrawName(anyInt())).thenReturn(name);
+
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
+
+ assertThat(scanner.getName(NULL_PATH)).isEqualTo(name);
+ }
+
+ @Test
public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() {
Mockito.doNothing().when(mBrailleDisplayConnection).disconnect();
mBrailleDisplayConnection.write(
@@ -201,6 +213,38 @@
verify(mBrailleDisplayConnection).disconnect();
}
+ @Test
+ public void connect_unableToGetUniq_usesNameFallback() throws Exception {
+ try {
+ IBrailleDisplayController controller =
+ Mockito.mock(IBrailleDisplayController.class);
+ final Path path = Path.of("/dev/null");
+ final String macAddress = "00:11:22:33:AA:BB";
+ final String name = "My Braille Display";
+ final byte[] descriptor = {0x05, 0x41};
+ Bundle bd = new Bundle();
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH,
+ path.toString());
+ bd.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR,
+ descriptor);
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name);
+ bd.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, true);
+ bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, null);
+ BrailleDisplayConnection.BrailleDisplayScanner scanner =
+ mBrailleDisplayConnection.setTestData(List.of(bd));
+ // Validate that the test data is set up correctly before attempting connection:
+ assertThat(scanner.getUniqueId(path)).isNull();
+ assertThat(scanner.getName(path)).isEqualTo(name);
+
+ mBrailleDisplayConnection.connectLocked(
+ macAddress, name, BrailleDisplayConnection.BUS_BLUETOOTH, controller);
+
+ verify(controller).onConnected(eq(mBrailleDisplayConnection), eq(descriptor));
+ } finally {
+ mBrailleDisplayConnection.disconnect();
+ }
+ }
+
// BrailleDisplayConnection#setTestData() is used to enable CTS testing with
// test Braille display data, but its own implementation should also be tested
// so that issues in this helper don't cause confusing failures in CTS.
@@ -220,6 +264,9 @@
String uniq1 = "uniq1", uniq2 = "uniq2";
bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1);
bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2);
+ String name1 = "name1", name2 = "name2";
+ bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name1);
+ bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name2);
int bus1 = BrailleDisplayConnection.BUS_USB, bus2 =
BrailleDisplayConnection.BUS_BLUETOOTH;
bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH,
@@ -235,6 +282,8 @@
expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2);
expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1);
expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2);
+ expect.that(scanner.getName(path1)).isEqualTo(name1);
+ expect.that(scanner.getName(path2)).isEqualTo(name2);
expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1);
expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 1a51c45..58567ca 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -1272,7 +1272,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsEnabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void onFullscreenMagnificationActivationState_systemUiBorderFlagOn_notifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
TEST_DISPLAY, /* activated= */ true);
@@ -1282,7 +1282,7 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void
onFullscreenMagnificationActivationState_systemUiBorderFlagOff_neverNotifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 643dcec..0a2a855 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -123,6 +123,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -678,6 +679,87 @@
}
/**
+ * Test that, when exceeding the maximum number of running users, a profile of the current user
+ * is not stopped.
+ */
+ @Test
+ public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
+ throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+
+ final int PARENT_ID = 200;
+ final int PROFILE1_ID = 201;
+ final int PROFILE2_ID = 202;
+ final int FG_USER_ID = 300;
+ final int BG_USER_ID = 400;
+
+ setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE1_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE2_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(FG_USER_ID, 0).profileGroupId = FG_USER_ID;
+ setUpUser(BG_USER_ID, 0).profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ int numberOfUserSwitches = 1;
+ addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ assertTrue(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertThat(mUserController.startProfile(PROFILE1_ID, true, null)).isTrue();
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ numberOfUserSwitches++;
+ addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ assertTrue(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, BG_USER_ID, FG_USER_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ // Now we exceed the maxRunningUsers parameter (of 5):
+ assertThat(mUserController.startProfile(PROFILE2_ID, true, null)).isTrue();
+ // Currently, starting a profile doesn't trigger evaluating whether we've exceeded max, so
+ // we expect no users to be stopped. This policy may change in the future. Log but no fail.
+ if (!newHashSet(SYSTEM_USER_ID, PROFILE1_ID, BG_USER_ID, PROFILE2_ID, PARENT_ID, FG_USER_ID)
+ .equals(new HashSet<>(mUserController.getRunningUsersLU()))) {
+ Log.w(TAG, "Starting a profile that exceeded max running users didn't lead to "
+ + "expectations: " + mUserController.getRunningUsersLU());
+ }
+
+ numberOfUserSwitches++;
+ addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
+ numberOfUserSwitches, false);
+ mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
+ waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+ // We've now done a user switch and should notice that we've exceeded the maximum number of
+ // users. The oldest background user should be stopped (BG_USER); even though PROFILE1 was
+ // older, it should not be stopped since it's a profile of the (new) current user.
+ assertFalse(mUserController.canStartMoreUsers());
+ assertEquals(newHashSet(
+ SYSTEM_USER_ID, PROFILE1_ID, PROFILE2_ID, FG_USER_ID, PARENT_ID),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ }
+
+ /**
* Test that, in getRunningUsersLU, parents come after their profile, even if the profile was
* started afterwards.
*/
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 83bbd0e..23728db 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -35,6 +35,7 @@
import static android.media.AudioManager.STREAM_RING;
import static android.media.AudioManager.STREAM_SYSTEM;
import static android.media.AudioManager.STREAM_VOICE_CALL;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
@@ -47,6 +48,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -66,6 +68,8 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
@@ -214,6 +218,19 @@
reset(mSpyAudioSystem);
+ final boolean useFixedVolume = mContext.getResources().getBoolean(
+ Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
+ final PackageManager packageManager = mContext.getPackageManager();
+ final boolean isTelevision = packageManager != null
+ && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+ final boolean isSingleVolume = mContext.getResources().getBoolean(
+ Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
+ final boolean automotiveHardened = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE) && autoPublicVolumeApiHardening();
+ assumeFalse("Skipping test for fixed, TV, single volume and auto devices",
+ useFixedVolume || isTelevision || isSingleVolume || automotiveHardened);
+
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED,
Manifest.permission.MODIFY_AUDIO_ROUTING,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index f0dc5f0..7e04277 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -269,7 +269,7 @@
mFingerprintSensorConfigurationsCaptor.capture());
final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
- .getSensorPairForInstance("defaultHIDL").second;
+ .getSensorPropForInstance("defaultHIDL");
assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
assertEquals(fingerprintProp[0].commonProps.sensorStrength,
@@ -280,7 +280,7 @@
final android.hardware.biometrics.face.SensorProps[] faceProp =
mFaceSensorConfigurationsCaptor.getValue()
- .getSensorPairForInstance("defaultHIDL").second;
+ .getSensorPropForInstance("defaultHIDL");
assertEquals(faceProp[0].commonProps.sensorId, faceId);
assertEquals(faceProp[0].commonProps.sensorStrength,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 527bc5b..f6da411 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -20,6 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.eq;
@@ -29,7 +30,9 @@
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.hardware.biometrics.BiometricConstants;
import android.os.Handler;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -72,6 +75,8 @@
@Mock
private BaseClientMonitor mNonInterruptableClientMonitor;
@Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
private ClientMonitorCallback mClientCallback;
@Mock
private ClientMonitorCallback mOnStartCallback;
@@ -435,17 +440,18 @@
}
@Test
- public void cancelWatchdogWhenStarted() {
+ public void cancelWatchdogWhenStarted() throws RemoteException {
cancelWatchdog(true);
}
@Test
- public void cancelWatchdogWithoutStarting() {
+ public void cancelWatchdogWithoutStarting() throws RemoteException {
cancelWatchdog(false);
}
- private void cancelWatchdog(boolean start) {
+ private void cancelWatchdog(boolean start) throws RemoteException {
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
+ when(mInterruptableClientMonitor.getListener()).thenReturn(mListener);
mInterruptableOperation.start(mOnStartCallback);
if (start) {
@@ -461,6 +467,8 @@
assertThat(mInterruptableOperation.isFinished()).isTrue();
assertThat(mInterruptableOperation.isCanceling()).isFalse();
+ verify(mInterruptableClientMonitor.getListener()).onError(anyInt(), anyInt(), eq(
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0));
verify(mOnStartCallback).onClientFinished(eq(mInterruptableClientMonitor), eq(false));
verify(mInterruptableClientMonitor).destroy();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 981eba5..971323a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -135,6 +135,8 @@
private ISession mSession;
@Mock
private IFingerprint mFingerprint;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
@Before
public void setUp() {
@@ -206,7 +208,7 @@
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
+ createBaseClientMonitor(), mock(ClientMonitorCallback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
assertEquals(1, mScheduler.mPendingOperations.size());
@@ -244,7 +246,7 @@
// Pretend the scheduler is busy so the first operation doesn't start right away. We want
// to pretend like there are two operations in the queue before kicking things off
mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
+ createBaseClientMonitor(), mock(ClientMonitorCallback.class));
mScheduler.scheduleClientMonitor(client1, callback1);
assertEquals(1, mScheduler.mPendingOperations.size());
@@ -612,10 +614,10 @@
@Test
public void testInterruptPrecedingClients_whenExpected() {
- final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interruptableMonitor = createBaseClientMonitor();
when(interruptableMonitor.isInterruptable()).thenReturn(true);
- final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interrupter = createBaseClientMonitor();
when(interrupter.interruptsPrecedingClients()).thenReturn(true);
mScheduler.scheduleClientMonitor(interruptableMonitor);
@@ -628,10 +630,10 @@
@Test
public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
- final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interruptableMonitor = createBaseClientMonitor();
when(interruptableMonitor.isInterruptable()).thenReturn(true);
- final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+ final BaseClientMonitor interrupter = createBaseClientMonitor();
when(interrupter.interruptsPrecedingClients()).thenReturn(false);
mScheduler.scheduleClientMonitor(interruptableMonitor);
@@ -741,7 +743,7 @@
//Start watchdog
mScheduler.startWatchdog();
waitForIdle();
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
waitForIdle();
@@ -775,9 +777,9 @@
//Start watchdog
mScheduler.startWatchdog();
waitForIdle();
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
- mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class),
+ mScheduler.scheduleClientMonitor(createBaseClientMonitor(),
mock(ClientMonitorCallback.class));
waitForIdle();
@@ -857,7 +859,7 @@
public void testScheduleOperation_whenNoUser() {
mCurrentUserId = UserHandle.USER_NULL;
- final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ final BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(0);
mScheduler.scheduleClientMonitor(nextClient);
@@ -875,9 +877,9 @@
mStartOperationsFinish = false;
final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class)
+ createBaseClientMonitor(),
+ createBaseClientMonitor(),
+ createBaseClientMonitor()
};
for (BaseClientMonitor client : nextClients) {
when(client.getTargetUserId()).thenReturn(5);
@@ -899,7 +901,7 @@
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
- final BaseClientMonitor client = mock(BaseClientMonitor.class);
+ final BaseClientMonitor client = createBaseClientMonitor();
when(client.getTargetUserId()).thenReturn(5);
@@ -913,7 +915,7 @@
assertThat(mScheduler.mCurrentOperation).isNull();
final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
+ createBaseClientMonitor(), new ClientMonitorCallback() {});
mScheduler.mCurrentOperation = fakeOperation;
startUserClient.mCallback.onClientFinished(startUserClient, true);
@@ -925,7 +927,7 @@
public void testScheduleOperation_whenSameUser() {
mCurrentUserId = 10;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
mScheduler.scheduleClientMonitor(nextClient);
@@ -943,7 +945,7 @@
mCurrentUserId = 10;
final int nextUserId = 11;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(nextUserId);
mScheduler.scheduleClientMonitor(nextClient);
@@ -963,7 +965,7 @@
public void testStartUser_alwaysStartsNextOperation() {
mCurrentUserId = UserHandle.USER_NULL;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(10);
mScheduler.scheduleClientMonitor(nextClient);
@@ -977,7 +979,7 @@
// schedule second operation but swap out the current operation
// before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
+ nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(11);
mScheduler.scheduleClientMonitor(nextClient);
@@ -994,7 +996,7 @@
// When a stop user client fails, check that mStopUserClient
// is set to null to prevent the scheduler from getting stuck.
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ BaseClientMonitor nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(10);
mScheduler.scheduleClientMonitor(nextClient);
@@ -1008,7 +1010,7 @@
// schedule second operation but swap out the current operation
// before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
+ nextClient = createBaseClientMonitor();
when(nextClient.getTargetUserId()).thenReturn(11);
mShouldFailStopUser = true;
mScheduler.scheduleClientMonitor(nextClient);
@@ -1023,6 +1025,13 @@
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
+ private BaseClientMonitor createBaseClientMonitor() {
+ BaseClientMonitor client = mock(BaseClientMonitor.class);
+ when(client.getListener()).thenReturn(mListener);
+
+ return client;
+ }
+
private void waitForIdle() {
TestableLooper.get(this).processAllMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 3aaac2e..e015e97 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -32,8 +32,6 @@
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.face.IFace;
-import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -93,18 +91,12 @@
@Mock
private FaceProvider mFaceProviderVirtual;
@Mock
- private IFace mDefaultFaceDaemon;
- @Mock
- private IFace mVirtualFaceDaemon;
- @Mock
private IBiometricService mIBiometricService;
@Mock
private IBinder mToken;
@Mock
private IFaceServiceReceiver mFaceServiceReceiver;
- private final SensorProps mDefaultSensorProps = new SensorProps();
- private final SensorProps mVirtualSensorProps = new SensorProps();
private FaceService mFaceService;
private final FaceSensorPropertiesInternal mSensorPropsDefault =
new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
@@ -126,10 +118,6 @@
@Before
public void setUp() throws RemoteException {
- when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
- new SensorProps[]{mDefaultSensorProps});
- when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
- new SensorProps[]{mVirtualSensorProps});
when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
when(mFaceProviderDefault.containsSensor(anyInt()))
@@ -140,15 +128,7 @@
mContext.getTestablePermissions().setPermission(
USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
- mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
- (name) -> {
- if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
- return mDefaultFaceDaemon;
- } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
- return mVirtualFaceDaemon;
- }
- return null;
- });
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL});
}
private void initService() {
@@ -199,15 +179,7 @@
@RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
- mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
- (name) -> {
- if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
- return mDefaultFaceDaemon;
- } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
- return mVirtualFaceDaemon;
- }
- return null;
- });
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL});
initService();
mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 88956b6..20961a9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -43,8 +43,6 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
-import android.hardware.biometrics.fingerprint.IFingerprint;
-import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -123,10 +121,6 @@
private IBinder mToken;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
- @Mock
- private IFingerprint mDefaultFingerprintDaemon;
- @Mock
- private IFingerprint mVirtualFingerprintDaemon;
@Captor
private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -145,8 +139,6 @@
false /* resetLockoutRequiresHardwareAuthToken */);
private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
private FingerprintService mService;
- private final SensorProps mDefaultSensorProps = new SensorProps();
- private final SensorProps mVirtualSensorProps = new SensorProps();
@Before
public void setup() throws Exception {
@@ -159,10 +151,6 @@
.thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
when(mFingerprintVirtual.containsSensor(anyInt()))
.thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
- when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
- new SensorProps[]{mDefaultSensorProps});
- when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
- new SensorProps[]{mVirtualSensorProps});
mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -177,15 +165,7 @@
mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
true /* resetLockoutRequiresHardwareAuthToken */);
- mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
- (name) -> {
- if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
- return mDefaultFingerprintDaemon;
- } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
- return mVirtualFingerprintDaemon;
- }
- return null;
- });
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL});
}
private void initServiceWith(String... aidlInstances) {
@@ -270,15 +250,7 @@
public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFingerprintSensorConfigurations =
new FingerprintSensorConfigurations(true);
- mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
- (name) -> {
- if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
- return mDefaultFingerprintDaemon;
- } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
- return mVirtualFingerprintDaemon;
- }
- return null;
- });
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL});
initServiceWith(NAME_VIRTUAL);
mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5e0806d..7d90a8b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -2004,7 +2004,7 @@
mRunningAppsChangedCallback,
params,
new DisplayManagerGlobal(mIDisplayManager),
- new VirtualCameraController(DEVICE_POLICY_DEFAULT));
+ new VirtualCameraController(DEVICE_POLICY_DEFAULT, virtualDeviceId));
mVdms.addVirtualDevice(virtualDeviceImpl);
assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
assertThat(virtualDeviceImpl.getPersistentDeviceId())
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 9ca1df0..4505a8b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -62,6 +63,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualCameraControllerTest {
+ private static final int DEVICE_ID = 5;
private static final String CAMERA_NAME_1 = "Virtual camera 1";
private static final int CAMERA_WIDTH_1 = 100;
private static final int CAMERA_HEIGHT_1 = 200;
@@ -91,8 +93,9 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock,
- DEVICE_POLICY_CUSTOM);
- when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
+ DEVICE_POLICY_CUSTOM, DEVICE_ID);
+ when(mVirtualCameraServiceMock.registerCamera(any(), any(), anyInt())).thenReturn(true);
+ when(mVirtualCameraServiceMock.getCameraId(any())).thenReturn(0);
}
@After
@@ -109,7 +112,10 @@
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
- verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture());
+ ArgumentCaptor<Integer> deviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture(),
+ deviceIdCaptor.capture());
+ assertThat(deviceIdCaptor.getValue()).isEqualTo(DEVICE_ID);
VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
@@ -146,8 +152,11 @@
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
+ ArgumentCaptor<Integer> deviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(),
- configurationCaptor.capture());
+ configurationCaptor.capture(), deviceIdCaptor.capture());
+ List<Integer> deviceIds = deviceIdCaptor.getAllValues();
+ assertThat(deviceIds).containsExactly(DEVICE_ID, DEVICE_ID);
List<VirtualCameraConfiguration> virtualCameraConfigurations =
configurationCaptor.getAllValues();
assertThat(virtualCameraConfigurations).hasSize(2);
@@ -177,8 +186,8 @@
@Test
public void registerCamera_withDefaultCameraPolicy_throwsException(int lensFacing) {
mVirtualCameraController.close();
- mVirtualCameraController = new VirtualCameraController(
- mVirtualCameraServiceMock, DEVICE_POLICY_DEFAULT);
+ mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock,
+ DEVICE_POLICY_DEFAULT, DEVICE_ID);
assertThrows(IllegalArgumentException.class,
() -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
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 1591a96..4405a20 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -180,25 +180,26 @@
// Test that only one clone user can be created
final int mainUserId = mainUser.getIdentifier();
- UserInfo userInfo = createProfileForUser("Clone user1",
+ UserInfo cloneProfileUser = createProfileForUser("Clone user1",
UserManager.USER_TYPE_PROFILE_CLONE,
mainUserId);
- assertThat(userInfo).isNotNull();
- UserInfo userInfo2 = createProfileForUser("Clone user2",
+ assertThat(cloneProfileUser).isNotNull();
+ UserInfo cloneProfileUser2 = createProfileForUser("Clone user2",
UserManager.USER_TYPE_PROFILE_CLONE,
mainUserId);
- assertThat(userInfo2).isNull();
+ assertThat(cloneProfileUser2).isNull();
- final Context userContext = mContext.createPackageContextAsUser("system", 0,
- UserHandle.of(userInfo.id));
- assertThat(userContext.getSystemService(
+ final Context profileUserContest = mContext.createPackageContextAsUser("system", 0,
+ UserHandle.of(cloneProfileUser.id));
+ final UserManager profileUM = UserManager.get(profileUserContest);
+ assertThat(profileUserContest.getSystemService(
UserManager.class).isMediaSharedWithParent()).isTrue();
- assertThat(Settings.Secure.getInt(userContext.getContentResolver(),
+ assertThat(Settings.Secure.getInt(profileUserContest.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0)).isEqualTo(1);
List<UserInfo> list = mUserManager.getUsers();
List<UserInfo> cloneUsers = list.stream().filter(
- user -> (user.id == userInfo.id && user.name.equals("Clone user1")
+ user -> (user.id == cloneProfileUser.id && user.name.equals("Clone user1")
&& user.isCloneProfile()))
.collect(Collectors.toList());
assertThat(cloneUsers.size()).isEqualTo(1);
@@ -206,7 +207,7 @@
// Check that the new clone user has the expected properties (relative to the defaults)
// provided that the test caller has the necessary permissions.
UserProperties cloneUserProperties =
- mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+ mUserManager.getUserProperties(UserHandle.of(cloneProfileUser.id));
assertThat(typeProps.getUseParentsContacts())
.isEqualTo(cloneUserProperties.getUseParentsContacts());
assertThat(typeProps.getShowInLauncher())
@@ -226,15 +227,15 @@
assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
cloneUserProperties.getProfileApiVisibility());
- compareDrawables(mUserManager.getUserBadge(),
+ compareDrawables(profileUM.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
// Verify clone user parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
- UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+ UserInfo parentProfileInfo = mUserManager.getProfileParent(cloneProfileUser.id);
assertThat(parentProfileInfo).isNotNull();
assertThat(mainUserId).isEqualTo(parentProfileInfo.id);
- removeUser(userInfo.id);
+ removeUser(cloneProfileUser.id);
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
}
@@ -322,19 +323,22 @@
// Test that only one private profile can be created
final int mainUserId = mainUser.getIdentifier();
- UserInfo userInfo = createProfileForUser("Private profile1",
+ UserInfo privateProfileUser = createProfileForUser("Private profile1",
UserManager.USER_TYPE_PROFILE_PRIVATE,
mainUserId);
- assertThat(userInfo).isNotNull();
- UserInfo userInfo2 = createProfileForUser("Private profile2",
+ assertThat(privateProfileUser).isNotNull();
+ UserInfo privateProfileUser2 = createProfileForUser("Private profile2",
UserManager.USER_TYPE_PROFILE_PRIVATE,
mainUserId);
- assertThat(userInfo2).isNull();
+ assertThat(privateProfileUser2).isNull();
+ final UserManager profileUM = UserManager.get(
+ mContext.createPackageContextAsUser("android", 0,
+ UserHandle.of(privateProfileUser.id)));
// Check that the new private profile has the expected properties (relative to the defaults)
// provided that the test caller has the necessary permissions.
UserProperties privateProfileUserProperties =
- mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+ mUserManager.getUserProperties(UserHandle.of(privateProfileUser.id));
assertThat(typeProps.getShowInLauncher())
.isEqualTo(privateProfileUserProperties.getShowInLauncher());
assertThrows(SecurityException.class, privateProfileUserProperties::getStartWithParent);
@@ -356,17 +360,17 @@
privateProfileUserProperties.getProfileApiVisibility());
assertThat(typeProps.areItemsRestrictedOnHomeScreen())
.isEqualTo(privateProfileUserProperties.areItemsRestrictedOnHomeScreen());
- compareDrawables(mUserManager.getUserBadge(),
+ compareDrawables(profileUM.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
// Verify private profile parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
- UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
+ UserInfo parentProfileInfo = mUserManager.getProfileParent(privateProfileUser.id);
assertThat(parentProfileInfo).isNotNull();
assertThat(mainUserId).isEqualTo(parentProfileInfo.id);
- removeUser(userInfo.id);
+ removeUser(privateProfileUser.id);
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
- assertThat(mUserManager.getProfileLabel()).isEqualTo(
+ assertThat(profileUM.getProfileLabel()).isEqualTo(
Resources.getSystem().getString(userTypeDetails.getLabel(0)));
}
@@ -964,10 +968,13 @@
assertThat(userTypeDetails.getName()).isEqualTo(UserManager.USER_TYPE_PROFILE_MANAGED);
int mainUserId = mUserManager.getMainUser().getIdentifier();
- UserInfo userInfo = createProfileForUser("Managed",
+ UserInfo managedProfileUser = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, mainUserId);
- assertThat(userInfo).isNotNull();
- final int userId = userInfo.id;
+ assertThat(managedProfileUser).isNotNull();
+ final int userId = managedProfileUser.id;
+ final UserManager profileUM = UserManager.get(
+ mContext.createPackageContextAsUser("android", 0,
+ UserHandle.of(managedProfileUser.id)));
assertThat(mUserManager.hasBadge(userId)).isEqualTo(userTypeDetails.hasBadge());
assertThat(mUserManager.getUserIconBadgeResId(userId))
@@ -978,10 +985,10 @@
.isEqualTo(userTypeDetails.getBadgeNoBackground());
assertThat(mUserManager.getUserStatusBarIconResId(userId))
.isEqualTo(userTypeDetails.getStatusBarIcon());
- compareDrawables(mUserManager.getUserBadge(),
+ compareDrawables(profileUM.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
- final int badgeIndex = userInfo.profileBadge;
+ final int badgeIndex = managedProfileUser.profileBadge;
assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
Resources.getSystem().getColor(userTypeDetails.getBadgeColor(badgeIndex), null));
assertThat(mUserManager.getUserBadgeDarkColor(userId)).isEqualTo(
@@ -1013,10 +1020,10 @@
// Create an actual user (of this user type) and get its properties.
int mainUserId = mUserManager.getMainUser().getIdentifier();
- final UserInfo userInfo = createProfileForUser("Managed",
+ final UserInfo managedProfileUser = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, mainUserId);
- assertThat(userInfo).isNotNull();
- final int userId = userInfo.id;
+ assertThat(managedProfileUser).isNotNull();
+ final int userId = managedProfileUser.id;
final UserProperties userProps = mUserManager.getUserProperties(UserHandle.of(userId));
// Check that this new user has the expected properties (relative to the defaults)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index acac63c..0d6fdc9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -65,6 +65,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.graphics.drawable.Icon;
@@ -212,7 +213,9 @@
// TODO (b/291907312): remove feature flag
// Disable feature flags by default. Tests should enable as needed.
mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
- Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
+ Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS,
+ Flags.FLAG_VIBRATE_WHILE_UNLOCKED,
+ Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
mNotificationInstanceIdSequence));
@@ -410,8 +413,8 @@
boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
boolean isLeanback, UserHandle userHandle) {
return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
- defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle,
- mPkg);
+ defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, false,
+ userHandle, mPkg);
}
private NotificationRecord getNotificationRecord(int id,
@@ -419,6 +422,16 @@
boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
boolean isLeanback, UserHandle userHandle, String packageName) {
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
+ defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, false,
+ userHandle, packageName);
+ }
+
+ private NotificationRecord getNotificationRecord(int id,
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, boolean isConversation, UserHandle userHandle, String packageName) {
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
@@ -426,6 +439,10 @@
.setPriority(Notification.PRIORITY_HIGH)
.setOnlyAlertOnce(once);
+ if (isConversation) {
+ builder.setStyle(new Notification.MessagingStyle("test user"));
+ }
+
int defaults = 0;
if (noisy) {
if (defaultSound) {
@@ -485,6 +502,19 @@
return r;
}
+ private NotificationRecord getConversationNotificationRecord(int id,
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, UserHandle userHandle, String packageName, String shortcutId) {
+ NotificationRecord r = getNotificationRecord(id, insistent, once, noisy, buzzy, lights,
+ defaultVibration, defaultSound, defaultLights, groupKey, groupAlertBehavior,
+ isLeanback, true, userHandle, packageName);
+ ShortcutInfo.Builder sb = new ShortcutInfo.Builder(getContext());
+ r.setShortcutInfo(sb.setId(shortcutId).build());
+ return r;
+ }
+
//
// Convenience functions for interacting with mocks
//
@@ -2057,12 +2087,14 @@
// set up internal state
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
// update should beep at 50% volume
r.isUpdate = true;
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
verifyBeepVolume(0.5f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
@@ -2070,7 +2102,7 @@
verifyBeepVolume(0.0f);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -2091,6 +2123,7 @@
// set up internal state
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
// Use different package for next notifications
@@ -2101,6 +2134,7 @@
// update should beep at 50% volume
mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
verifyBeepVolume(0.5f);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2113,7 +2147,7 @@
verifyBeepVolume(0.0f);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ assertEquals(-1, r3.getLastAudiblyAlertedMs());
}
@Test
@@ -2158,6 +2192,117 @@
}
@Test
+ public void testBeepVolume_politeNotif_AvalancheStrategy_AttnUpdate() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // Use different package for next notifications
+ NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // update should beep at 0% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.0f);
+
+ // Use different package for next notifications
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");
+
+ // 2nd update should beep at 0% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.0f);
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ assertEquals(-1, r3.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_AvalancheStrategy_exempt_AttnUpdate()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ NotificationRecord r = getBeepyNotification();
+ r.getNotification().category = Notification.CATEGORY_EVENT;
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // Use different package for next notifications
+ NotificationRecord r2 = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg",
+ "shortcut");
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // Use different package for next notifications
+ mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");
+
+ r3.getNotification().category = Notification.CATEGORY_REMINDER;
+
+ // Should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // Same package as r3 for next notifications
+ NotificationRecord r4 = getConversationNotificationRecord(mId, false /* insistent */,
+ false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true,
+ true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg",
+ "shortcut");
+
+ // 2nd update should beep at 50% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r4, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r4.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
@@ -2181,11 +2326,13 @@
// update should beep at 50% volume
NotificationRecord r2 = getBeepyNotification();
mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
verifyBeepVolume(0.5f);
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertEquals(-1, r2.getLastAudiblyAlertedMs());
verifyBeepVolume(0.0f);
// Use different package for next notifications
@@ -2199,7 +2346,7 @@
verifyBeepVolume(1.0f);
verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
}
@Test
@@ -2227,11 +2374,13 @@
// update should beep at 100% volume
NotificationRecord r2 = getBeepyNotification();
mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
verifyBeepVolume(1.0f);
// 2nd update should beep at 50% volume
Mockito.reset(mRingtonePlayer);
mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
verifyBeepVolume(0.5f);
// Use different package for next notifications
@@ -2246,7 +2395,7 @@
verifyBeepVolume(1.0f);
verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
}
@Test
@@ -2355,12 +2504,14 @@
// set up internal state
mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
// update should beep at 50% volume
r.isUpdate = true;
mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
verifyBeepVolume(0.5f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
@@ -2368,7 +2519,7 @@
verifyBeepVolume(0.0f);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ef879ee..39a962d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5681,8 +5681,7 @@
mService.mLocaleChangeReceiver.onReceive(mContext,
new Intent(Intent.ACTION_LOCALE_CHANGED));
- verify(mZenModeHelper, times(1)).updateDefaultZenRules(
- anyInt());
+ verify(mZenModeHelper).updateZenRulesOnLocaleChange();
}
private void simulateNotificationTimeoutBroadcast(String notificationKey) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
new file mode 100644
index 0000000..e782461
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.content.res.Configuration;
+import android.os.LocaleList;
+import android.service.notification.SystemZenRules;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.EventInfo;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+
+import com.android.internal.R;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+public class SystemZenRulesTest extends UiServiceTestCase {
+
+ private static final ScheduleInfo SCHEDULE_INFO;
+ private static final EventInfo EVENT_INFO;
+
+ static {
+ SCHEDULE_INFO = new ScheduleInfo();
+ SCHEDULE_INFO.days = new int[] { Calendar.WEDNESDAY };
+ SCHEDULE_INFO.startHour = 8;
+ SCHEDULE_INFO.endHour = 9;
+ EVENT_INFO = new EventInfo();
+ EVENT_INFO.calendarId = 1L;
+ EVENT_INFO.calName = "myCalendar";
+ }
+
+ @Before
+ public void setUp() {
+ Configuration config = new Configuration();
+ config.setLocales(new LocaleList(Locale.US));
+ mContext.getOrCreateTestableResources().overrideConfiguration(config);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.zen_mode_trigger_summary_range_symbol_combination, "%1$s-%2$s");
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.zen_mode_trigger_summary_divider_text, ",");
+ }
+
+ @Test
+ public void maybeUpgradeRules_oldSystemRules_upgraded() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule calendarRule = new ZenRule();
+ calendarRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ calendarRule.conditionId = ZenModeConfig.toEventConditionId(EVENT_INFO);
+ config.automaticRules.put("calendar", calendarRule);
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule.type).isEqualTo(AutomaticZenRule.TYPE_SCHEDULE_TIME);
+ assertThat(timeRule.triggerDescription).isNotEmpty();
+ assertThat(calendarRule.type).isEqualTo(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR);
+ assertThat(timeRule.triggerDescription).isNotEmpty();
+ }
+
+ @Test
+ public void maybeUpgradeRules_newSystemRules_untouched() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ timeRule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule original = timeRule.copy();
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule).isEqualTo(original);
+ }
+
+ @Test
+ public void maybeUpgradeRules_appOwnedRules_untouched() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = "some_other_package";
+ timeRule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule original = timeRule.copy();
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule).isEqualTo(original);
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_noOrSingleDays() {
+ // Test various cases for grouping and not-grouping of days.
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // No days
+ scheduleInfo.days = new int[]{};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)).isNull();
+
+ // A single day at the beginning of the week
+ scheduleInfo.days = new int[]{Calendar.SUNDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun,10:00 AM-4:00 PM");
+
+ // A single day in the middle of the week
+ scheduleInfo.days = new int[]{Calendar.THURSDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Thu,10:00 AM-4:00 PM");
+
+ // A single day at the end of the week
+ scheduleInfo.days = new int[]{Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sat,10:00 AM-4:00 PM");
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_oneGroup() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // The whole week
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Sat,10:00 AM-4:00 PM");
+
+ // Various cases of one big group
+ // Sunday through Thursday
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Thu,10:00 AM-4:00 PM");
+
+ // Wednesday through Saturday
+ scheduleInfo.days = new int[] {Calendar.WEDNESDAY, Calendar.THURSDAY,
+ Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Wed-Sat,10:00 AM-4:00 PM");
+
+ // Monday through Friday
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Mon-Fri,10:00 AM-4:00 PM");
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_mixedDays() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // cases combining groups and single days scattered around
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun,Tue-Thu,Sat,10:00 AM-4:00 PM");
+
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Wed,Fri-Sat,10:00 AM-4:00 PM");
+
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.WEDNESDAY,
+ Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Mon,Wed,Fri-Sat,10:00 AM-4:00 PM");
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f07472..3df52c7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.SystemZenRules;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
@@ -193,6 +194,7 @@
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -1087,6 +1089,11 @@
mZenModeHelper.mConfig.manualRule.enabled = true;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
@@ -1404,6 +1411,13 @@
mZenModeHelper.readXml(parser, true, 11);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, config10);
+ SystemZenRules.maybeUpgradeRules(mContext, config11);
+ }
+
assertEquals(
"Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
@@ -1483,6 +1497,11 @@
mZenModeHelper.mConfig.automaticRules = automaticRules;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -1494,7 +1513,8 @@
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertEquals("Automatic rules mismatch", original, current);
+ assertEquals("Automatic rules mismatch: current vs expected: "
+ + new ZenModeDiff.RuleDiff(original, current), original, current);
}
@Test
@@ -1524,6 +1544,11 @@
mZenModeHelper.mConfig.automaticRules = automaticRules;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
TypedXmlPullParser parser = getParserForByteStream(baos);
@@ -1532,7 +1557,8 @@
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertEquals("Automatic rules mismatch", original, current);
+ assertEquals("Automatic rules mismatch: current vs expected: "
+ + new ZenModeDiff.RuleDiff(original, current), original, current);
}
@Test
@@ -2112,7 +2138,7 @@
ZenModeConfig config = new ZenModeConfig();
config.automaticRules = new ArrayMap<>();
mZenModeHelper.mConfig = config;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); // shouldn't throw null pointer
+ mZenModeHelper.updateZenRulesOnLocaleChange(); // shouldn't throw null pointer
mZenModeHelper.pullRules(events); // shouldn't throw null pointer
}
@@ -2137,33 +2163,7 @@
autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
mZenModeHelper.mConfig.automaticRules = autoRules;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
- assertEquals(updatedDefaultRule,
- mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
- }
-
- @Test
- public void testDoNotUpdateEnabledDefaultAutoRule() {
- // mDefaultConfig is set to default config in setup by getDefaultConfigParser
- when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PERMISSION_GRANTED);
-
- // shouldn't update the rule that's enabled
- ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
- updatedDefaultRule.enabled = true;
- updatedDefaultRule.modified = false;
- updatedDefaultRule.creationTime = 0;
- updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
- updatedDefaultRule.name = "Schedule Default Rule";
- updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
- updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
-
- ArrayMap<String, ZenModeConfig.ZenRule> autoRules = new ArrayMap<>();
- autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
- mZenModeHelper.mConfig.automaticRules = autoRules;
-
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
+ mZenModeHelper.updateZenRulesOnLocaleChange();
assertEquals(updatedDefaultRule,
mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
}
@@ -2177,27 +2177,35 @@
// will update rule that is not enabled and modified
ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
+ customDefaultRule.pkg = SystemZenRules.PACKAGE_ANDROID;
customDefaultRule.enabled = false;
customDefaultRule.modified = false;
customDefaultRule.creationTime = 0;
customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
customDefaultRule.name = "Schedule Default Rule";
customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.SUNDAY };
+ scheduleInfo.startHour = 18;
+ scheduleInfo.endHour = 19;
+ customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(scheduleInfo);
customDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
ArrayMap<String, ZenModeConfig.ZenRule> autoRules = new ArrayMap<>();
autoRules.put(SCHEDULE_DEFAULT_RULE_ID, customDefaultRule);
mZenModeHelper.mConfig.automaticRules = autoRules;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
+ mZenModeHelper.updateZenRulesOnLocaleChange();
ZenModeConfig.ZenRule ruleAfterUpdating =
mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID);
assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled);
assertEquals(customDefaultRule.modified, ruleAfterUpdating.modified);
assertEquals(customDefaultRule.id, ruleAfterUpdating.id);
assertEquals(customDefaultRule.conditionId, ruleAfterUpdating.conditionId);
- assertFalse(Objects.equals(defaultRuleName, ruleAfterUpdating.name)); // update name
+ assertNotEquals(defaultRuleName, ruleAfterUpdating.name); // update name
+ if (Flags.modesUi()) {
+ assertThat(ruleAfterUpdating.triggerDescription).isNotEmpty(); // update trigger desc
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 173a1b6..1355092 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1634,6 +1634,28 @@
assertThat(transition.isInTransientHide(top.getTask())).isTrue();
}
+ /**
+ * Tests ATMS#startActivityWithScreenshot should collect display content for creating snapshot.
+ */
+ @Test
+ public void testActivityStartWithScreenshot() {
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ starter.setFreezeScreen(true);
+
+ registerTestTransitionPlayer();
+
+ final Intent intent = new Intent();
+ intent.setComponent(ActivityBuilder.getDefaultComponent());
+ starter.setReason("testActivityStartWithScreenshot")
+ .setIntent(intent)
+ .execute();
+
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ final Transition transition = controller.getCollectingTransition();
+ final Transition.ChangeInfo targetChangeInfo = transition.mChanges.get(mDisplayContent);
+ assertThat(targetChangeInfo).isNotNull();
+ }
+
@Test
public void testActivityStart_expectAddedToRecentTask() {
RecentTasks recentTasks = mock(RecentTasks.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index ed99108..400f9bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -38,18 +38,22 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransactionItem;
@@ -62,6 +66,7 @@
import android.os.LocaleList;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayInfo;
@@ -1099,4 +1104,61 @@
verify(mClientLifecycleManager).onLayoutContinued();
}
+
+ @Test
+ public void testGetTaskDescriptionIcon_matchingUid() {
+ // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+ doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+ final String filePath = "abc/def";
+ // Create an activity with a task description at the test icon filepath
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setUid(android.os.Process.myUid())
+ .setCreateTask(true)
+ .build();
+ final TaskDescription td = new TaskDescription.Builder().build();
+ td.setIconFilename(filePath);
+ activity.setTaskDescription(td);
+
+ // Verify this calls and does not throw a security exception
+ try {
+ mAtm.getTaskDescriptionIcon(filePath, activity.mUserId);
+ } catch (SecurityException e) {
+ fail("Unexpected security exception: " + e);
+ } catch (IllegalArgumentException e) {
+ // Ok, the file doesn't actually exist
+ }
+ }
+
+ @Test
+ public void testGetTaskDescriptionIcon_noMatchingActivity_expectException() {
+ // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+ doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+ final String filePath = "abc/def";
+
+ // Verify this throws a security exception due to no matching activity
+ assertThrows(SecurityException.class,
+ () -> mAtm.getTaskDescriptionIcon(filePath, UserHandle.myUserId()));
+ }
+
+ @Test
+ public void testGetTaskDescriptionIcon_noMatchingUid_expectException() {
+ // Ensure that we do not hold MANAGE_ACTIVITY_TASKS
+ doThrow(new SecurityException()).when(mAtm).enforceActivityTaskPermission(any());
+
+ final String filePath = "abc/def";
+ // Create an activity with a task description at the test icon filepath
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .setUid(101010)
+ .build();
+ final TaskDescription td = new TaskDescription.Builder().build();
+ td.setIconFilename(filePath);
+ activity.setTaskDescription(td);
+
+ // Verify this throws a security exception due to no matching UID
+ assertThrows(SecurityException.class,
+ () -> mAtm.getTaskDescriptionIcon(filePath, activity.mUserId));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 363ae14..21251c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -537,7 +537,7 @@
}).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any());
addToWindowMap(appWindow, true);
- dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient);
+ dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null);
OnBackInvokedCallback appCallback = createBackCallback(appLatch);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index bfa191e..75b84d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1472,7 +1472,6 @@
assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
assertSecurityException(expectCallable,
() -> mAtm.unregisterTaskStackListener(null));
- assertSecurityException(expectCallable, () -> mAtm.getTaskDescription(0));
assertSecurityException(expectCallable, () -> mAtm.cancelTaskWindowTransition(0));
assertSecurityException(expectCallable, () -> mAtm.startRecentsActivity(null, 0,
null));
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 85172e0..856ad2a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -928,6 +928,17 @@
}
@Test
+ public void testIsLetterboxed_activityFromBubble_returnsFalse() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity);
+ doReturn(true).when(mActivity).getLaunchedFromBubble();
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertFalse(mActivity.areBoundsLetterboxed());
+ }
+
+ @Test
public void testAspectRatioMatchParentBoundsAndImeAttachable() {
setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2000).build());
prepareUnresizable(mActivity, 2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index a88680a..1ca808f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1960,6 +1960,27 @@
verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
}
+ @Test
+ public void testGetBottomMostActivityInSamePackage() {
+ final String packageName = "homePackage";
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(false).build();
+ task.realActivity = new ComponentName(packageName, packageName + ".root_activity");
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord activityDifferentPackage =
+ new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord activitySamePackage =
+ new ActivityBuilder(mAtm)
+ .setComponent(new ComponentName(packageName, packageName + ".activity2"))
+ .setTask(task).build();
+
+ assertEquals(fragment1.getChildAt(0), task.getBottomMostActivity());
+ assertEquals(activitySamePackage, task.getBottomMostActivityInSamePackage());
+ assertNotEquals(activityDifferentPackage, task.getBottomMostActivityInSamePackage());
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 55a00fc..48fc2dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -799,6 +799,7 @@
verify(child).onConfigurationChanged(any());
}
+ @SuppressWarnings("SelfComparison")
@Test
public void testCompareTo() {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 48b12f7..e37da20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1115,7 +1115,7 @@
argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
}
- @RequiresFlagsDisabled(Flags.FLAG_MAGNIFICATION_ALWAYS_DRAW_FULLSCREEN_BORDER)
+ @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
@Test
public void testDrawMagnifiedViewport() {
final int displayId = mDisplayContent.mDisplayId;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index df32fbd..ebdd556 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8013,6 +8013,27 @@
KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL =
KEY_PREFIX + "scan_limited_service_after_volte_failure_bool";
+ /**
+ * This config defines {@link ImsReasonInfo} code with which the emergency call
+ * shall be retried.
+ *
+ * <p>
+ * If the reason code is one of the following, the emergency call shall be retried
+ * regardless of this configuration.
+ * <ul>
+ * <li>{@link ImsReasonInfo#CODE_LOCAL_CALL_CS_RETRY_REQUIRED}</li>
+ * <li>{@link ImsReasonInfo#CODE_LOCAL_NOT_REGISTERED}</li>
+ * <li>{@link ImsReasonInfo#CODE_SIP_ALTERNATE_EMERGENCY_CALL}</li>
+ * </ul>
+ * <p>
+ *
+ * This config is empty by default.
+ *
+ * @hide
+ */
+ public static final String KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY =
+ KEY_PREFIX + "ims_reasoninfo_code_to_retry_emergency_int_array";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -8085,6 +8106,8 @@
defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
true);
defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false);
+ defaults.putIntArray(KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY,
+ new int[0]);
return defaults;
}
@@ -9836,7 +9859,7 @@
* An integer key holds the time interval for refreshing or re-querying the satellite
* entitlement status from the entitlement server to ensure it is the latest.
*
- * The default value is 30 days (1 month).
+ * The default value is 7 days.
*/
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
@@ -9854,6 +9877,19 @@
"satellite_entitlement_supported_bool";
/**
+ * Indicates the appName that is used when querying the entitlement server for satellite.
+ *
+ * The default value is androidSatmode.
+ *
+ * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management.
+ * `app_name` is an optional attribute in the request and may vary depending on the carrier
+ * requirement.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING =
+ "satellite_entitlement_app_name_string";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10995,8 +11031,9 @@
CellSignalStrengthLte.USE_RSRP);
sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true);
- sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+ sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 7);
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+ sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp
new file mode 100644
index 0000000..236d704
--- /dev/null
+++ b/test-legacy/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+java_library {
+ name: "android.test.legacy",
+ sdk_version: "current",
+ libs: [
+ "android.test.mock.stubs",
+ "junit",
+ ],
+ static_libs: [
+ "android.test.base-minus-junit",
+ "android.test.runner-minus-junit",
+ ],
+ dist: {
+ targets: [
+ "sdk",
+ ],
+ },
+}
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
deleted file mode 100644
index da9dc25..0000000
--- a/test-legacy/Android.mk
+++ /dev/null
@@ -1,50 +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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
-ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
-
-# Build the android.test.legacy library
-# =====================================
-# Built against the SDK so that it can be statically included in APKs
-# without breaking link type checks.
-#
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := android.test.legacy
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_LIBRARIES := junit android.test.mock.stubs
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android.test.base-minus-junit \
- android.test.runner-minus-junit \
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-$(call declare-license-metadata,$(full_classes_jar),\
- SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS,\
- notice,$(LOCAL_PATH)/../NOTICE,Android,frameworks/base)
-
-# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
-
-endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/testables/OWNERS b/tests/testables/OWNERS
new file mode 100644
index 0000000..a6f1632
--- /dev/null
+++ b/tests/testables/OWNERS
@@ -0,0 +1 @@
+file:/packages/SystemUI/OWNERS
\ No newline at end of file
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index 0ec106e..384a21e 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -26,6 +26,8 @@
import org.mockito.invocation.InvocationOnMock;
+import java.util.Arrays;
+
/**
* Provides a version of Resources that defaults to all existing resources, but can have ids
* changed to return specific values.
@@ -103,6 +105,15 @@
if (index >= 0) {
Object value = mOverrides.valueAt(index);
if (value == null) throw new Resources.NotFoundException();
+ // Support for Resources.getString(resId, Object... formatArgs)
+ if (value instanceof String
+ && invocationOnMock.getMethod().getName().equals("getString")
+ && invocationOnMock.getArguments().length > 1) {
+ value = String.format(mResources.getConfiguration().getLocales().get(0),
+ (String) value,
+ Arrays.copyOfRange(invocationOnMock.getArguments(), 1,
+ invocationOnMock.getArguments().length));
+ }
return value;
}
} catch (Resources.NotFoundException e) {
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index 5a0f742..1d3e4bd 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -39,7 +39,7 @@
"androidx.test.core",
"frameworks-base-testutils",
"guava",
- "mockito-target-minus-junit4",
+ "mockito-target-extended-minus-junit4",
"truth",
],
@@ -48,6 +48,12 @@
"android.test.base",
],
+ // Required by Extended Mockito
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
test_suites: [
"general-tests",
],
diff --git a/wifi/tests/AndroidManifest.xml b/wifi/tests/AndroidManifest.xml
index 18986fc..66056e5 100644
--- a/wifi/tests/AndroidManifest.xml
+++ b/wifi/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.net.wifi.nonupdatable.test">
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
<activity android:label="WifiTestDummyLabel"
android:name="WifiTestDummyName"