Merge "Exempt autofill service to record sensitive content" into main
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 b897c1a..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);
@@ -5561,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/core/api/current.txt b/core/api/current.txt
index b8c2d90..1cfc025 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9242,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;
}
}
@@ -17982,7 +17982,6 @@
method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily build();
method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
- method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean canBuildVariableFamily();
}
public final class FontStyle {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a73aa71..ca5d3eb 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3977,6 +3977,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/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/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/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/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/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/provider/Settings.java b/core/java/android/provider/Settings.java
index c74d50a..363e252 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1002,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.
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index dd548c6..49d4af8 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;
@@ -1148,6 +1151,7 @@
private String mLargestViewTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
@@ -1155,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.
@@ -1202,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));
@@ -3177,7 +3184,7 @@
== LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = PACKAGE)
public InsetsController getInsetsController() {
return mInsetsController;
}
@@ -12144,7 +12151,8 @@
+ "IWindow:%s Session:%s",
mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession));
}
- mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
+ mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow,
+ mImeBackAnimationController);
}
private void sendBackKeyEvent(int action) {
@@ -12788,7 +12796,9 @@
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- // return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
+ }
return false;
}
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/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/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/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/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/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/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/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/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/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index e441c53..199e929 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -120,24 +120,6 @@
}
/**
- * Returns true if the passed font files can be used for building a variable font family
- * that automatically adjust the `wght` and `ital` axes value for the requested
- * weight/italic style values.
- *
- * This method can be used for checking that the provided font files can be used for
- * building a variable font family created with {@link #buildVariableFamily()}.
- * If this function returns false, the {@link #buildVariableFamily()} will fail and
- * return null.
- *
- * @return true if a variable font can be built from the given fonts. Otherwise, false.
- */
- @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
- public boolean canBuildVariableFamily() {
- int variableFamilyType = analyzeAndResolveVariableType(mFonts);
- return variableFamilyType != VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
- }
-
- /**
* Build a variable font family that automatically adjust the `wght` and `ital` axes value
* for the requested weight/italic style values.
*
@@ -158,9 +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}. Please check
- * that your font files meet the above requirements or consider using the {@link #build()}
- * method.
+ * 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 9b14ce4..b749a06 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -165,15 +165,27 @@
},
}
+filegroup {
+ name: "wm_shell-shared-aidls",
+
+ srcs: [
+ "shared/**/*.aidl",
+ ],
+
+ path: "shared/src",
+}
+
java_library {
name: "WindowManager-Shell-shared",
srcs: [
"shared/**/*.java",
"shared/**/*.kt",
+ ":wm_shell-shared-aidls",
],
static_libs: [
"androidx.dynamicanimation_dynamicanimation",
+ "jsr330",
],
}
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/common/annotations/ShellBackgroundThread.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/ShellBackgroundThread.java
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java
index 4cd3c90..a1496ac 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/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 shared background 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 ShellBackgroundThread {
-}
+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..ad3be3d 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;
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..3253cac 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
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/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/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/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..ae07812 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;
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/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/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/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/jni/Android.bp b/media/jni/Android.bp
index d6d74e8..94fce79 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -122,9 +122,6 @@
"-Wunused",
"-Wunreachable-code",
],
-
- // TODO(b/330503129) Workaround build breakage.
- lto_O0: true,
}
cc_library_shared {
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 7caa9e4..cf5059c 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -44,7 +44,4 @@
"-Wunreachable-code",
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
-
- // TODO(b/330503129) Workaround LTO build breakage.
- lto_O0: true,
}
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/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/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 b8d4469..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,10 +65,10 @@
* 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.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/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 503779c..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
@@ -482,7 +482,7 @@
Card(
modifier = Modifier.height(Dimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, colors.primaryFixedDim),
+ border = BorderStroke(3.dp, colors.secondary),
shape = RoundedCornerShape(size = 80.dp)
) {
Column(
@@ -495,7 +495,7 @@
text = stringResource(R.string.title_for_empty_state_cta),
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
- color = colors.secondaryFixed,
+ color = colors.secondary,
)
Row(
modifier = Modifier.fillMaxWidth(),
@@ -505,8 +505,8 @@
modifier = Modifier.height(56.dp),
colors =
ButtonDefaults.buttonColors(
- containerColor = colors.primaryFixed,
- contentColor = colors.onPrimaryFixed,
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
),
onClick = {
viewModel.onOpenWidgetEditor(
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/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/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/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c80835d..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
@@ -286,6 +287,7 @@
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
centralSurfaces = mock(),
headsUpInteractor = kosmos.headsUpNotificationInteractor,
+ occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
)
startable.start()
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 594543b..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,
)
}
@@ -204,6 +207,28 @@
}
@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
fun startsInLockscreenScene() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
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/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 2195849..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] -->
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/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8f1a5f7..985f6c8 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -454,6 +454,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 {
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/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/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/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/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/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/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/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/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/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/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/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..1fb701e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -0,0 +1,95 @@
+/*
+ * 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.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";
+
+ @Inject
+ public HearingDevicesTile(
+ QSHost host,
+ QsEventLogger uiEventLogger,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
+ FalsingManager falsingManager,
+ MetricsLogger metricsLogger,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter,
+ QSLogger qsLogger
+ ) {
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ statusBarStateController, activityStarter, qsLogger);
+ }
+
+ @Override
+ public State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleClick(@Nullable View 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/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 42b41f8..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() {
@@ -130,32 +132,36 @@
.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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b796a20..597e773 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -41,7 +41,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;
@@ -85,7 +84,6 @@
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;
@@ -442,16 +440,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,
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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8d66fa7..e4f5aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4974,6 +4974,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/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/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/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/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/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 9df6f93..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
@@ -445,7 +445,7 @@
// 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,
@@ -455,7 +455,7 @@
goneToDreamingTransitionViewModel.lockscreenAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
- lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
+ lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
@@ -589,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/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/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 579d43b..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;
@@ -465,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();
@@ -720,10 +701,6 @@
mRemoved = true;
}
- public void setRevealParameters(@Nullable RevealParams revealParams) {
- mRevealParams = revealParams;
- }
-
@Override
public void dispatchStartTemporaryDetach() {
super.dispatchStartTemporaryDetach();
@@ -1178,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/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/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/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
index 58011eb..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
@@ -27,7 +27,7 @@
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 f5c9bef..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
@@ -30,7 +30,7 @@
private val onBackAnimationCallback =
onBackAnimationCallbackFrom(
- backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics),
+ backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi { displayMetrics },
displayMetrics = displayMetrics,
onBackProgressed = onBackProgress,
onBackStarted = onBackStart,
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/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/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
new file mode 100644
index 0000000..326df5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 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 androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+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;
+
+ private TestableLooper mTestableLooper;
+ private HearingDevicesTile mTile;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+
+ mTile = new HearingDevicesTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger);
+
+ 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);
+ }
+}
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 5b6da0e..e957ca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -897,8 +897,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..29a92d9 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);
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/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/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/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/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/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/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/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/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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ed1a763..b00676a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1680,6 +1680,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 +2312,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 +5060,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
@@ -18189,6 +18226,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) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c2613bc..60a8b50 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -671,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
@@ -877,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
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/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/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/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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 095a233..fd3da85 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6272,9 +6272,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);
+ }
}
});
}
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/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/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..6f1c834 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6799,6 +6799,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/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/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/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/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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index df32fbd..ed3d5d5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9854,6 +9854,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.
*
@@ -10997,6 +11010,7 @@
sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true);
sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
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/tools/app_metadata_bundles/Android.bp b/tools/app_metadata_bundles/Android.bp
index be6bea6..a012dca 100644
--- a/tools/app_metadata_bundles/Android.bp
+++ b/tools/app_metadata_bundles/Android.bp
@@ -5,6 +5,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_preload_safety",
}
java_library_host {
@@ -24,3 +25,15 @@
"asllib",
],
}
+
+java_test_host {
+ name: "aslgen-test",
+ srcs: ["src/test/java/**/*.java"],
+ exclude_srcs: [
+ ],
+ java_resource_dirs: ["src/test/resources"],
+ static_libs: [
+ "aslgen",
+ "junit",
+ ],
+}
diff --git a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
index fb7a6ab..d7edfd4 100644
--- a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
+++ b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java
@@ -16,8 +16,8 @@
package com.android.aslgen;
-import com.android.asllib.AndroidSafetyLabel;
-import com.android.asllib.AndroidSafetyLabel.Format;
+import com.android.asllib.AslConverter;
+import com.android.asllib.AslConverter.Format;
import com.android.asllib.util.MalformedXmlException;
import org.xml.sax.SAXException;
@@ -41,9 +41,8 @@
String inFile = null;
String outFile = null;
- Format inFormat = Format.NULL;
- Format outFormat = Format.NULL;
-
+ Format inFormat = AslConverter.Format.NULL;
+ Format outFormat = AslConverter.Format.NULL;
// Except for "--help", all arguments require a value currently.
// So just make sure we have an even number and
@@ -79,11 +78,11 @@
throw new IllegalArgumentException("output file is required");
}
- if (inFormat == Format.NULL) {
+ if (inFormat == AslConverter.Format.NULL) {
throw new IllegalArgumentException("input format is required");
}
- if (outFormat == Format.NULL) {
+ if (outFormat == AslConverter.Format.NULL) {
throw new IllegalArgumentException("output format is required");
}
@@ -92,24 +91,23 @@
System.out.println("in format: " + inFormat);
System.out.println("out format: " + outFormat);
- var asl = AndroidSafetyLabel.readFromStream(new FileInputStream(inFile), inFormat);
- asl.writeToStream(new FileOutputStream(outFile), outFormat);
+ var asl = AslConverter.readFromStream(new FileInputStream(inFile), inFormat);
+ AslConverter.writeToStream(new FileOutputStream(outFile), asl, outFormat);
}
private static Format getFormat(String argValue) {
if ("hr".equals(argValue)) {
- return Format.HUMAN_READABLE;
+ return AslConverter.Format.HUMAN_READABLE;
} else if ("od".equals(argValue)) {
- return Format.ON_DEVICE;
+ return AslConverter.Format.ON_DEVICE;
} else {
- return Format.NULL;
+ return AslConverter.Format.NULL;
}
}
private static void showUsage() {
- AndroidSafetyLabel.test();
System.err.println(
- "Usage:\n"
- );
+ "Usage: aslgen --in-path [input-file] --out-path [output-file] --in-format [hr|od]"
+ + " --out-format [hr|od]");
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
index bc8063e..cdb559b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
@@ -16,107 +16,47 @@
package com.android.asllib;
-import com.android.asllib.util.MalformedXmlException;
-
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.util.List;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
public class AndroidSafetyLabel implements AslMarshallable {
- public enum Format {
- NULL, HUMAN_READABLE, ON_DEVICE;
- }
-
+ private final Long mVersion;
+ private final SystemAppSafetyLabel mSystemAppSafetyLabel;
private final SafetyLabels mSafetyLabels;
+ private final TransparencyInfo mTransparencyInfo;
public SafetyLabels getSafetyLabels() {
return mSafetyLabels;
}
- public AndroidSafetyLabel(SafetyLabels safetyLabels) {
+ public AndroidSafetyLabel(
+ Long version,
+ SystemAppSafetyLabel systemAppSafetyLabel,
+ SafetyLabels safetyLabels,
+ TransparencyInfo transparencyInfo) {
+ this.mVersion = version;
+ this.mSystemAppSafetyLabel = systemAppSafetyLabel;
this.mSafetyLabels = safetyLabels;
- }
-
- /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
- // TODO(b/329902686): Support parsing from on-device.
- public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
- throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- Document document = factory.newDocumentBuilder().parse(in);
-
- switch (format) {
- case HUMAN_READABLE:
- Element appMetadataBundles =
- XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
-
- return new AndroidSafetyLabelFactory()
- .createFromHrElements(
- List.of(
- XmlUtils.getSingleElement(
- document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES)));
- case ON_DEVICE:
- throw new IllegalArgumentException(
- "Parsing from on-device format is not supported at this time.");
- default:
- throw new IllegalStateException("Unrecognized input format.");
- }
- }
-
- /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
- // TODO(b/329902686): Support outputting human-readable format.
- public void writeToStream(OutputStream out, Format format)
- throws IOException, ParserConfigurationException, TransformerException {
- var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
- var document = docBuilder.newDocument();
-
- switch (format) {
- case HUMAN_READABLE:
- throw new IllegalArgumentException(
- "Outputting human-readable format is not supported at this time.");
- case ON_DEVICE:
- for (var child : this.toOdDomElements(document)) {
- document.appendChild(child);
- }
- break;
- default:
- throw new IllegalStateException("Unrecognized input format.");
- }
-
- TransformerFactory transformerFactory = TransformerFactory.newInstance();
- Transformer transformer = transformerFactory.newTransformer();
- transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
- transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
- StreamResult streamResult = new StreamResult(out); // out
- DOMSource domSource = new DOMSource(document);
- transformer.transform(domSource, streamResult);
+ this.mTransparencyInfo = transparencyInfo;
}
/** Creates an on-device DOM element from an {@link AndroidSafetyLabel} */
@Override
public List<Element> toOdDomElements(Document doc) {
Element aslEle = doc.createElement(XmlUtils.OD_TAG_BUNDLE);
- XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
- return List.of(aslEle);
- }
-
- public static void test() {
- // TODO(b/329902686): Add tests.
+ aslEle.appendChild(XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
+ if (mSafetyLabels != null) {
+ XmlUtils.appendChildren(aslEle, mSafetyLabels.toOdDomElements(doc));
+ }
+ if (mSystemAppSafetyLabel != null) {
+ XmlUtils.appendChildren(aslEle, mSystemAppSafetyLabel.toOdDomElements(doc));
+ }
+ if (mTransparencyInfo != null) {
+ XmlUtils.appendChildren(aslEle, mTransparencyInfo.toOdDomElements(doc));
+ }
+ return XmlUtils.listOf(aslEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
index 7e7fcf9..3dc725b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
@@ -29,11 +29,29 @@
public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles)
throws MalformedXmlException {
Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles);
+ long version = XmlUtils.tryGetVersion(appMetadataBundlesEle);
+
Element safetyLabelsEle =
XmlUtils.getSingleChildElement(
- appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS);
+ appMetadataBundlesEle, XmlUtils.HR_TAG_SAFETY_LABELS, false);
SafetyLabels safetyLabels =
- new SafetyLabelsFactory().createFromHrElements(List.of(safetyLabelsEle));
- return new AndroidSafetyLabel(safetyLabels);
+ new SafetyLabelsFactory().createFromHrElements(XmlUtils.listOf(safetyLabelsEle));
+
+ Element systemAppSafetyLabelEle =
+ XmlUtils.getSingleChildElement(
+ appMetadataBundlesEle, XmlUtils.HR_TAG_SYSTEM_APP_SAFETY_LABEL, false);
+ SystemAppSafetyLabel systemAppSafetyLabel =
+ new SystemAppSafetyLabelFactory()
+ .createFromHrElements(XmlUtils.listOf(systemAppSafetyLabelEle));
+
+ Element transparencyInfoEle =
+ XmlUtils.getSingleChildElement(
+ appMetadataBundlesEle, XmlUtils.HR_TAG_TRANSPARENCY_INFO, false);
+ TransparencyInfo transparencyInfo =
+ new TransparencyInfoFactory()
+ .createFromHrElements(XmlUtils.listOf(transparencyInfoEle));
+
+ return new AndroidSafetyLabel(
+ version, systemAppSafetyLabel, safetyLabels, transparencyInfo);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
new file mode 100644
index 0000000..f94b659
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
@@ -0,0 +1,143 @@
+/*
+ * 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.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** AppInfo representation */
+public class AppInfo implements AslMarshallable {
+ private final String mTitle;
+ private final String mDescription;
+ private final Boolean mContainsAds;
+ private final Boolean mObeyAps;
+ private final Boolean mAdsFingerprinting;
+ private final Boolean mSecurityFingerprinting;
+ private final String mPrivacyPolicy;
+ private final List<String> mSecurityEndpoints;
+ private final List<String> mFirstPartyEndpoints;
+ private final List<String> mServiceProviderEndpoints;
+ private final String mCategory;
+ private final String mEmail;
+ private final String mWebsite;
+
+ public AppInfo(
+ String title,
+ String description,
+ Boolean containsAds,
+ Boolean obeyAps,
+ Boolean adsFingerprinting,
+ Boolean securityFingerprinting,
+ String privacyPolicy,
+ List<String> securityEndpoints,
+ List<String> firstPartyEndpoints,
+ List<String> serviceProviderEndpoints,
+ String category,
+ String email,
+ String website) {
+ this.mTitle = title;
+ this.mDescription = description;
+ this.mContainsAds = containsAds;
+ this.mObeyAps = obeyAps;
+ this.mAdsFingerprinting = adsFingerprinting;
+ this.mSecurityFingerprinting = securityFingerprinting;
+ this.mPrivacyPolicy = privacyPolicy;
+ this.mSecurityEndpoints = securityEndpoints;
+ this.mFirstPartyEndpoints = firstPartyEndpoints;
+ this.mServiceProviderEndpoints = serviceProviderEndpoints;
+ this.mCategory = category;
+ this.mEmail = email;
+ this.mWebsite = website;
+ }
+
+ /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element appInfoEle = XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_APP_INFO);
+ if (this.mTitle != null) {
+ appInfoEle.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_TITLE, mTitle));
+ }
+ if (this.mDescription != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_DESCRIPTION, mDescription));
+ }
+ if (this.mContainsAds != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_CONTAINS_ADS, mContainsAds));
+ }
+ if (this.mObeyAps != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(doc, XmlUtils.OD_NAME_OBEY_APS, mObeyAps));
+ }
+ if (this.mAdsFingerprinting != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc, XmlUtils.OD_NAME_ADS_FINGERPRINTING, mAdsFingerprinting));
+ }
+ if (this.mSecurityFingerprinting != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdBooleanEle(
+ doc,
+ XmlUtils.OD_NAME_SECURITY_FINGERPRINTING,
+ mSecurityFingerprinting));
+ }
+ if (this.mPrivacyPolicy != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc, XmlUtils.OD_NAME_PRIVACY_POLICY, mPrivacyPolicy));
+ }
+ if (this.mSecurityEndpoints != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdArray(
+ doc,
+ XmlUtils.OD_TAG_STRING_ARRAY,
+ XmlUtils.OD_NAME_SECURITY_ENDPOINT,
+ mSecurityEndpoints));
+ }
+ if (this.mFirstPartyEndpoints != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdArray(
+ doc,
+ XmlUtils.OD_TAG_STRING_ARRAY,
+ XmlUtils.OD_NAME_FIRST_PARTY_ENDPOINT,
+ mFirstPartyEndpoints));
+ }
+ if (this.mServiceProviderEndpoints != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdArray(
+ doc,
+ XmlUtils.OD_TAG_STRING_ARRAY,
+ XmlUtils.OD_NAME_SERVICE_PROVIDER_ENDPOINT,
+ mServiceProviderEndpoints));
+ }
+ if (this.mCategory != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_CATEGORY, this.mCategory));
+ }
+ if (this.mEmail != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, this.mEmail));
+ }
+ if (this.mWebsite != null) {
+ appInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, this.mWebsite));
+ }
+ return XmlUtils.listOf(appInfoEle);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
new file mode 100644
index 0000000..26d94c1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
+
+ /** Creates a {@link AppInfo} from the human-readable DOM element. */
+ @Override
+ public AppInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
+ Element appInfoEle = XmlUtils.getSingleElement(elements);
+ if (appInfoEle == null) {
+ AslgenUtil.logI("No AppInfo found in hr format.");
+ return null;
+ }
+
+ String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE);
+ String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION);
+ Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS);
+ Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS);
+ Boolean adsFingerprinting =
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING);
+ Boolean securityFingerprinting =
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING);
+ String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY);
+ List<String> securityEndpoints =
+ Arrays.stream(
+ appInfoEle
+ .getAttribute(XmlUtils.HR_ATTR_SECURITY_ENDPOINTS)
+ .split("\\|"))
+ .toList();
+ List<String> firstPartyEndpoints =
+ Arrays.stream(
+ appInfoEle
+ .getAttribute(XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS)
+ .split("\\|"))
+ .toList();
+ List<String> serviceProviderEndpoints =
+ Arrays.stream(
+ appInfoEle
+ .getAttribute(XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS)
+ .split("\\|"))
+ .toList();
+ String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY);
+ String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL);
+ String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+
+ return new AppInfo(
+ title,
+ description,
+ containsAds,
+ obeyAps,
+ adsFingerprinting,
+ securityFingerprinting,
+ privacyPolicy,
+ securityEndpoints,
+ firstPartyEndpoints,
+ serviceProviderEndpoints,
+ category,
+ email,
+ website);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
new file mode 100644
index 0000000..9dd5531
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -0,0 +1,116 @@
+/*
+ * 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.asllib;
+
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class AslConverter {
+ public enum Format {
+ NULL,
+ HUMAN_READABLE,
+ ON_DEVICE;
+ }
+
+ /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */
+ // TODO(b/329902686): Support parsing from on-device.
+ public static AndroidSafetyLabel readFromStream(InputStream in, Format format)
+ throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(in);
+
+ switch (format) {
+ case HUMAN_READABLE:
+ Element appMetadataBundles =
+ XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES);
+
+ return new AndroidSafetyLabelFactory()
+ .createFromHrElements(XmlUtils.listOf(appMetadataBundles));
+ case ON_DEVICE:
+ throw new IllegalArgumentException(
+ "Parsing from on-device format is not supported at this time.");
+ default:
+ throw new IllegalStateException("Unrecognized input format.");
+ }
+ }
+
+ /** Reads a {@link AndroidSafetyLabel} from a String. */
+ public static AndroidSafetyLabel readFromString(String in, Format format)
+ throws IOException, ParserConfigurationException, SAXException, MalformedXmlException {
+ InputStream stream = new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8));
+ return readFromStream(stream, format);
+ }
+
+ /** Write the content of the {@link AndroidSafetyLabel} to a {@link OutputStream}. */
+ // TODO(b/329902686): Support outputting human-readable format.
+ public static void writeToStream(
+ OutputStream out, AndroidSafetyLabel asl, AslConverter.Format format)
+ throws IOException, ParserConfigurationException, TransformerException {
+ var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ var document = docBuilder.newDocument();
+
+ switch (format) {
+ case HUMAN_READABLE:
+ throw new IllegalArgumentException(
+ "Outputting human-readable format is not supported at this time.");
+ case ON_DEVICE:
+ for (var child : asl.toOdDomElements(document)) {
+ document.appendChild(child);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized input format.");
+ }
+
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ StreamResult streamResult = new StreamResult(out); // out
+ DOMSource domSource = new DOMSource(document);
+ transformer.transform(domSource, streamResult);
+ }
+
+ /** Get the content of the {@link AndroidSafetyLabel} as String. */
+ public static String getXmlAsString(AndroidSafetyLabel asl, AslConverter.Format format)
+ throws IOException, ParserConfigurationException, TransformerException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeToStream(out, asl, format);
+ return out.toString(StandardCharsets.UTF_8);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
index e5ed63b..b9e06fb 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
@@ -53,6 +53,6 @@
for (DataType dataType : mDataTypes.values()) {
XmlUtils.appendChildren(dataCategoryEle, dataType.toOdDomElements(doc));
}
- return List.of(dataCategoryEle);
+ return XmlUtils.listOf(dataCategoryEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
index d946345..ae7b603 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
@@ -36,7 +36,8 @@
throw new MalformedXmlException(
String.format("Unrecognized data type name: %s", dataTypeName));
}
- dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(ele)));
+ dataTypeMap.put(
+ dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele)));
}
return new DataCategory(categoryName, dataTypeMap);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
index d2fffc0..96ec93c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
@@ -74,7 +74,7 @@
maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
- return List.of(dataLabelsEle);
+ return XmlUtils.listOf(dataLabelsEle);
}
private void maybeAppendDataUsages(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
index 1adb140..0e14ebf 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
@@ -16,6 +16,7 @@
package com.android.asllib;
+import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import org.w3c.dom.Element;
@@ -33,6 +34,10 @@
@Override
public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
Element ele = XmlUtils.getSingleElement(elements);
+ if (ele == null) {
+ AslgenUtil.logI("Found no DataLabels in hr format.");
+ return null;
+ }
Map<String, DataCategory> dataAccessed =
getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED);
Map<String, DataCategory> dataCollected =
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
index 5ba2975..cecee39 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
@@ -29,15 +29,13 @@
public class DataType implements AslMarshallable {
public enum Purpose {
- PURPOSE_APP_FUNCTIONALITY(1),
- PURPOSE_ANALYTICS(2),
- PURPOSE_DEVELOPER_COMMUNICATIONS(3),
- PURPOSE_FRAUD_PREVENTION_SECURITY(4),
- PURPOSE_ADVERTISING(5),
- PURPOSE_PERSONALIZATION(6),
- PURPOSE_ACCOUNT_MANAGEMENT(7);
-
- private static final String PURPOSE_PREFIX = "PURPOSE_";
+ APP_FUNCTIONALITY(1),
+ ANALYTICS(2),
+ DEVELOPER_COMMUNICATIONS(3),
+ FRAUD_PREVENTION_SECURITY(4),
+ ADVERTISING(5),
+ PERSONALIZATION(6),
+ ACCOUNT_MANAGEMENT(7);
private final int mValue;
@@ -57,7 +55,7 @@
return e;
}
}
- throw new IllegalArgumentException("No enum for value: " + value);
+ throw new IllegalArgumentException("No Purpose enum for value: " + value);
}
/** Get the Purpose associated with the human-readable String. */
@@ -67,15 +65,12 @@
return e;
}
}
- throw new IllegalArgumentException("No enum for str: " + s);
+ throw new IllegalArgumentException("No Purpose enum for str: " + s);
}
/** Human-readable String representation of Purpose. */
public String toString() {
- if (!this.name().startsWith(PURPOSE_PREFIX)) {
- return this.name();
- }
- return this.name().substring(PURPOSE_PREFIX.length()).toLowerCase();
+ return this.name().toLowerCase();
}
}
@@ -139,16 +134,14 @@
public List<Element> toOdDomElements(Document doc) {
Element dataTypeEle = XmlUtils.createPbundleEleWithName(doc, this.getDataTypeName());
if (!this.getPurposeSet().isEmpty()) {
- Element purposesEle = doc.createElement(XmlUtils.OD_TAG_INT_ARRAY);
- purposesEle.setAttribute(XmlUtils.OD_ATTR_NAME, XmlUtils.OD_NAME_PURPOSES);
- purposesEle.setAttribute(
- XmlUtils.OD_ATTR_NUM, String.valueOf(this.getPurposeSet().size()));
- for (DataType.Purpose purpose : this.getPurposeSet()) {
- Element purposeEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
- purposeEle.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(purpose.getValue()));
- purposesEle.appendChild(purposeEle);
- }
- dataTypeEle.appendChild(purposesEle);
+ dataTypeEle.appendChild(
+ XmlUtils.createOdArray(
+ doc,
+ XmlUtils.OD_TAG_INT_ARRAY,
+ XmlUtils.OD_NAME_PURPOSES,
+ this.getPurposeSet().stream()
+ .map(p -> String.valueOf(p.getValue()))
+ .toList()));
}
maybeAddBoolToOdElement(
@@ -162,7 +155,7 @@
this.getIsSharingOptional(),
XmlUtils.OD_NAME_IS_SHARING_OPTIONAL);
maybeAddBoolToOdElement(doc, dataTypeEle, this.getEphemeral(), XmlUtils.OD_NAME_EPHEMERAL);
- return List.of(dataTypeEle);
+ return XmlUtils.listOf(dataTypeEle);
}
private static void maybeAddBoolToOdElement(
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
index e3d1587..bfa3303 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
@@ -34,13 +34,10 @@
.map(DataType.Purpose::forString)
.collect(Collectors.toUnmodifiableSet());
Boolean isCollectionOptional =
- XmlUtils.fromString(
- hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL));
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL);
Boolean isSharingOptional =
- XmlUtils.fromString(
- hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL));
- Boolean ephemeral =
- XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL));
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL);
+ Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL);
return new DataType(
dataTypeName, purposeSet, isCollectionOptional, isSharingOptional, ephemeral);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
new file mode 100644
index 0000000..44a5b12
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.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.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** DeveloperInfo representation */
+public class DeveloperInfo implements AslMarshallable {
+ public enum DeveloperRelationship {
+ OEM(0),
+ ODM(1),
+ SOC(2),
+ OTA(3),
+ CARRIER(4),
+ AOSP(5),
+ OTHER(6);
+
+ private final int mValue;
+
+ DeveloperRelationship(int value) {
+ this.mValue = value;
+ }
+
+ /** Get the int value associated with the DeveloperRelationship. */
+ public int getValue() {
+ return mValue;
+ }
+
+ /** Get the DeveloperRelationship associated with the int value. */
+ public static DeveloperInfo.DeveloperRelationship forValue(int value) {
+ for (DeveloperInfo.DeveloperRelationship e : values()) {
+ if (e.getValue() == value) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No DeveloperRelationship enum for value: " + value);
+ }
+
+ /** Get the DeveloperRelationship associated with the human-readable String. */
+ public static DeveloperInfo.DeveloperRelationship forString(String s) {
+ for (DeveloperInfo.DeveloperRelationship e : values()) {
+ if (e.toString().equals(s)) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("No DeveloperRelationship enum for str: " + s);
+ }
+
+ /** Human-readable String representation of DeveloperRelationship. */
+ public String toString() {
+ return this.name().toLowerCase();
+ }
+ }
+
+ private final String mName;
+ private final String mEmail;
+ private final String mAddress;
+ private final String mCountryRegion;
+ private final DeveloperRelationship mDeveloperRelationship;
+ private final String mWebsite;
+ private final String mAppDeveloperRegistryId;
+
+ public DeveloperInfo(
+ String name,
+ String email,
+ String address,
+ String countryRegion,
+ DeveloperRelationship developerRelationship,
+ String website,
+ String appDeveloperRegistryId) {
+ this.mName = name;
+ this.mEmail = email;
+ this.mAddress = address;
+ this.mCountryRegion = countryRegion;
+ this.mDeveloperRelationship = developerRelationship;
+ this.mWebsite = website;
+ this.mAppDeveloperRegistryId = appDeveloperRegistryId;
+ }
+
+ /** Creates an on-device DOM element from the {@link SafetyLabels}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element developerInfoEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DEVELOPER_INFO);
+ if (mName != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_NAME, mName));
+ }
+ if (mEmail != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_EMAIL, mEmail));
+ }
+ if (mAddress != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_ADDRESS, mAddress));
+ }
+ if (mCountryRegion != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc, XmlUtils.OD_NAME_COUNTRY_REGION, mCountryRegion));
+ }
+ if (mDeveloperRelationship != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdLongEle(
+ doc,
+ XmlUtils.OD_NAME_DEVELOPER_RELATIONSHIP,
+ mDeveloperRelationship.getValue()));
+ }
+ if (mWebsite != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_WEBSITE, mWebsite));
+ }
+ if (mAppDeveloperRegistryId != null) {
+ developerInfoEle.appendChild(
+ XmlUtils.createOdStringEle(
+ doc,
+ XmlUtils.OD_NAME_APP_DEVELOPER_REGISTRY_ID,
+ mAppDeveloperRegistryId));
+ }
+
+ return XmlUtils.listOf(developerInfoEle);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
new file mode 100644
index 0000000..4961892
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInfo> {
+
+ /** Creates a {@link DeveloperInfo} from the human-readable DOM element. */
+ @Override
+ public DeveloperInfo createFromHrElements(List<Element> elements) throws MalformedXmlException {
+ Element developerInfoEle = XmlUtils.getSingleElement(elements);
+ if (developerInfoEle == null) {
+ AslgenUtil.logI("No DeveloperInfo found in hr format.");
+ return null;
+ }
+ String name = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_NAME);
+ String email = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_EMAIL);
+ String address = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_ADDRESS);
+ String countryRegion =
+ XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_COUNTRY_REGION);
+ DeveloperInfo.DeveloperRelationship developerRelationship =
+ DeveloperInfo.DeveloperRelationship.forString(
+ XmlUtils.getStringAttr(
+ developerInfoEle, XmlUtils.HR_ATTR_DEVELOPER_RELATIONSHIP));
+ String website = XmlUtils.getStringAttr(developerInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
+ String appDeveloperRegistryId =
+ XmlUtils.getStringAttr(
+ developerInfoEle, XmlUtils.HR_ATTR_APP_DEVELOPER_REGISTRY_ID, false);
+
+ return new DeveloperInfo(
+ name,
+ email,
+ address,
+ countryRegion,
+ developerRelationship,
+ website,
+ appDeveloperRegistryId);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
index f06522f..40ef48d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
@@ -47,7 +47,11 @@
public List<Element> toOdDomElements(Document doc) {
Element safetyLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SAFETY_LABELS);
- XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
- return List.of(safetyLabelsEle);
+ safetyLabelsEle.appendChild(
+ XmlUtils.createOdLongEle(doc, XmlUtils.OD_NAME_VERSION, mVersion));
+ if (mDataLabels != null) {
+ XmlUtils.appendChildren(safetyLabelsEle, mDataLabels.toOdDomElements(doc));
+ }
+ return XmlUtils.listOf(safetyLabelsEle);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
index 80b9f57..ab81b1d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
@@ -16,6 +16,7 @@
package com.android.asllib;
+import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
import org.w3c.dom.Element;
@@ -28,18 +29,16 @@
@Override
public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException {
Element safetyLabelsEle = XmlUtils.getSingleElement(elements);
- Long version;
- try {
- version = Long.parseLong(safetyLabelsEle.getAttribute(XmlUtils.HR_ATTR_VERSION));
- } catch (Exception e) {
- throw new IllegalArgumentException(
- "Malformed or missing required version in safety labels.");
+ if (safetyLabelsEle == null) {
+ AslgenUtil.logI("No SafetyLabels found in hr format.");
+ return null;
}
+ long version = XmlUtils.tryGetVersion(safetyLabelsEle);
DataLabels dataLabels =
new DataLabelsFactory()
.createFromHrElements(
- List.of(
+ XmlUtils.listOf(
XmlUtils.getSingleChildElement(
safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS)));
return new SafetyLabels(version, dataLabels);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
new file mode 100644
index 0000000..93d9c2b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
+public class SystemAppSafetyLabel implements AslMarshallable {
+
+ private final String mUrl;
+
+ public SystemAppSafetyLabel(String url) {
+ this.mUrl = url;
+ }
+
+ /** Returns the system app safety label URL. */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /** Creates an on-device DOM element from the {@link SystemAppSafetyLabel}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element systemAppSafetyLabelEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_SYSTEM_APP_SAFETY_LABEL);
+ systemAppSafetyLabelEle.appendChild(
+ XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl));
+ return XmlUtils.listOf(systemAppSafetyLabelEle);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
new file mode 100644
index 0000000..c8c1c7b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.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.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<SystemAppSafetyLabel> {
+
+ /** Creates a {@link SystemAppSafetyLabel} from the human-readable DOM element. */
+ @Override
+ public SystemAppSafetyLabel createFromHrElements(List<Element> elements)
+ throws MalformedXmlException {
+ Element systemAppSafetyLabelEle = XmlUtils.getSingleElement(elements);
+ if (systemAppSafetyLabelEle == null) {
+ AslgenUtil.logI("No SystemAppSafetyLabel found in hr format.");
+ return null;
+ }
+
+ String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL);
+ return new SystemAppSafetyLabel(url);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
new file mode 100644
index 0000000..88717b9
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
@@ -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.
+ */
+
+package com.android.asllib;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/** TransparencyInfo representation containing {@link DeveloperInfo} and {@link AppInfo} */
+public class TransparencyInfo implements AslMarshallable {
+
+ private final DeveloperInfo mDeveloperInfo;
+ private final AppInfo mAppInfo;
+
+ public TransparencyInfo(DeveloperInfo developerInfo, AppInfo appInfo) {
+ this.mDeveloperInfo = developerInfo;
+ this.mAppInfo = appInfo;
+ }
+
+ /** Gets the {@link DeveloperInfo} of the {@link TransparencyInfo}. */
+ public DeveloperInfo getDeveloperInfo() {
+ return mDeveloperInfo;
+ }
+
+ /** Gets the {@link AppInfo} of the {@link TransparencyInfo}. */
+ public AppInfo getAppInfo() {
+ return mAppInfo;
+ }
+
+ /** Creates an on-device DOM element from the {@link TransparencyInfo}. */
+ @Override
+ public List<Element> toOdDomElements(Document doc) {
+ Element transparencyInfoEle =
+ XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_TRANSPARENCY_INFO);
+ if (mDeveloperInfo != null) {
+ XmlUtils.appendChildren(transparencyInfoEle, mDeveloperInfo.toOdDomElements(doc));
+ }
+ if (mAppInfo != null) {
+ XmlUtils.appendChildren(transparencyInfoEle, mAppInfo.toOdDomElements(doc));
+ }
+ return XmlUtils.listOf(transparencyInfoEle);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
new file mode 100644
index 0000000..13a7eb6
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.asllib;
+
+import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class TransparencyInfoFactory implements AslMarshallableFactory<TransparencyInfo> {
+
+ /** Creates a {@link TransparencyInfo} from the human-readable DOM element. */
+ @Override
+ public TransparencyInfo createFromHrElements(List<Element> elements)
+ throws MalformedXmlException {
+ Element transparencyInfoEle = XmlUtils.getSingleElement(elements);
+ if (transparencyInfoEle == null) {
+ AslgenUtil.logI("No TransparencyInfo found in hr format.");
+ return null;
+ }
+
+ Element developerInfoEle =
+ XmlUtils.getSingleChildElement(
+ transparencyInfoEle, XmlUtils.HR_TAG_DEVELOPER_INFO, false);
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromHrElements(XmlUtils.listOf(developerInfoEle));
+
+ Element appInfoEle =
+ XmlUtils.getSingleChildElement(
+ transparencyInfoEle, XmlUtils.HR_TAG_APP_INFO, false);
+ AppInfo appInfo = new AppInfoFactory().createFromHrElements(XmlUtils.listOf(appInfoEle));
+
+ return new TransparencyInfo(developerInfo, appInfo);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
index 3bc9ccc..cc8fe79 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
@@ -23,16 +23,27 @@
import org.w3c.dom.NodeList;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
public class XmlUtils {
public static final String HR_TAG_APP_METADATA_BUNDLES = "app-metadata-bundles";
+ public static final String HR_TAG_SYSTEM_APP_SAFETY_LABEL = "system-app-safety-label";
public static final String HR_TAG_SAFETY_LABELS = "safety-labels";
+ public static final String HR_TAG_TRANSPARENCY_INFO = "transparency-info";
+ public static final String HR_TAG_DEVELOPER_INFO = "developer-info";
+ public static final String HR_TAG_APP_INFO = "app-info";
public static final String HR_TAG_DATA_LABELS = "data-labels";
public static final String HR_TAG_DATA_ACCESSED = "data-accessed";
public static final String HR_TAG_DATA_COLLECTED = "data-collected";
public static final String HR_TAG_DATA_SHARED = "data-shared";
-
+ public static final String HR_ATTR_NAME = "name";
+ public static final String HR_ATTR_EMAIL = "email";
+ public static final String HR_ATTR_ADDRESS = "address";
+ public static final String HR_ATTR_COUNTRY_REGION = "countryRegion";
+ public static final String HR_ATTR_DEVELOPER_RELATIONSHIP = "relationship";
+ public static final String HR_ATTR_WEBSITE = "website";
+ public static final String HR_ATTR_APP_DEVELOPER_REGISTRY_ID = "registryId";
public static final String HR_ATTR_DATA_CATEGORY = "dataCategory";
public static final String HR_ATTR_DATA_TYPE = "dataType";
public static final String HR_ATTR_IS_COLLECTION_OPTIONAL = "isCollectionOptional";
@@ -40,16 +51,55 @@
public static final String HR_ATTR_EPHEMERAL = "ephemeral";
public static final String HR_ATTR_PURPOSES = "purposes";
public static final String HR_ATTR_VERSION = "version";
+ public static final String HR_ATTR_URL = "url";
+ public static final String HR_ATTR_TITLE = "title";
+ public static final String HR_ATTR_DESCRIPTION = "description";
+ public static final String HR_ATTR_CONTAINS_ADS = "containsAds";
+ public static final String HR_ATTR_OBEY_APS = "obeyAps";
+ public static final String HR_ATTR_ADS_FINGERPRINTING = "adsFingerprinting";
+ public static final String HR_ATTR_SECURITY_FINGERPRINTING = "securityFingerprinting";
+ public static final String HR_ATTR_PRIVACY_POLICY = "privacyPolicy";
+ public static final String HR_ATTR_SECURITY_ENDPOINTS = "securityEndpoints";
+ public static final String HR_ATTR_FIRST_PARTY_ENDPOINTS = "firstPartyEndpoints";
+ public static final String HR_ATTR_SERVICE_PROVIDER_ENDPOINTS = "serviceProviderEndpoints";
+ public static final String HR_ATTR_CATEGORY = "category";
public static final String OD_TAG_BUNDLE = "bundle";
public static final String OD_TAG_PBUNDLE_AS_MAP = "pbundle_as_map";
public static final String OD_TAG_BOOLEAN = "boolean";
+ public static final String OD_TAG_LONG = "long";
+ public static final String OD_TAG_STRING = "string";
public static final String OD_TAG_INT_ARRAY = "int-array";
+ public static final String OD_TAG_STRING_ARRAY = "string-array";
public static final String OD_TAG_ITEM = "item";
public static final String OD_ATTR_NAME = "name";
public static final String OD_ATTR_VALUE = "value";
public static final String OD_ATTR_NUM = "num";
public static final String OD_NAME_SAFETY_LABELS = "safety_labels";
+ public static final String OD_NAME_TRANSPARENCY_INFO = "transparency_info";
+ public static final String OD_NAME_DEVELOPER_INFO = "developer_info";
+ public static final String OD_NAME_NAME = "name";
+ public static final String OD_NAME_EMAIL = "email";
+ public static final String OD_NAME_ADDRESS = "address";
+ public static final String OD_NAME_COUNTRY_REGION = "country_region";
+ public static final String OD_NAME_DEVELOPER_RELATIONSHIP = "relationship";
+ public static final String OD_NAME_WEBSITE = "website";
+ public static final String OD_NAME_APP_DEVELOPER_REGISTRY_ID = "app_developer_registry_id";
+ public static final String OD_NAME_APP_INFO = "app_info";
+ public static final String OD_NAME_TITLE = "title";
+ public static final String OD_NAME_DESCRIPTION = "description";
+ public static final String OD_NAME_CONTAINS_ADS = "contains_ads";
+ public static final String OD_NAME_OBEY_APS = "obey_aps";
+ public static final String OD_NAME_ADS_FINGERPRINTING = "ads_fingerprinting";
+ public static final String OD_NAME_SECURITY_FINGERPRINTING = "security_fingerprinting";
+ public static final String OD_NAME_PRIVACY_POLICY = "privacy_policy";
+ public static final String OD_NAME_SECURITY_ENDPOINT = "security_endpoint";
+ public static final String OD_NAME_FIRST_PARTY_ENDPOINT = "first_party_endpoint";
+ public static final String OD_NAME_SERVICE_PROVIDER_ENDPOINT = "service_provider_endpoint";
+ public static final String OD_NAME_CATEGORY = "category";
+ public static final String OD_NAME_VERSION = "version";
+ public static final String OD_NAME_URL = "url";
+ public static final String OD_NAME_SYSTEM_APP_SAFETY_LABEL = "system_app_safety_label";
public static final String OD_NAME_DATA_LABELS = "data_labels";
public static final String OD_NAME_DATA_ACCESSED = "data_accessed";
public static final String OD_NAME_DATA_COLLECTED = "data_collected";
@@ -75,17 +125,39 @@
public static Element getSingleChildElement(Element parentEle, String tagName)
throws MalformedXmlException {
var elements = parentEle.getElementsByTagName(tagName);
- return getSingleElement(elements, tagName);
+ return getSingleElement(elements, tagName, true);
+ }
+
+ /**
+ * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}.
+ */
+ public static Element getSingleChildElement(Element parentEle, String tagName, boolean required)
+ throws MalformedXmlException {
+ var elements = parentEle.getElementsByTagName(tagName);
+ return getSingleElement(elements, tagName, required);
}
/** Gets the single {@link Element} from {@param elements} */
public static Element getSingleElement(NodeList elements, String tagName)
throws MalformedXmlException {
- if (elements.getLength() != 1) {
+ return getSingleElement(elements, tagName, true);
+ }
+
+ /** Gets the single {@link Element} from {@param elements} */
+ public static Element getSingleElement(NodeList elements, String tagName, boolean required)
+ throws MalformedXmlException {
+ if (elements.getLength() > 1) {
throw new MalformedXmlException(
String.format(
"Expected 1 element \"%s\" in NodeList but got %s.",
tagName, elements.getLength()));
+ } else if (elements.getLength() == 0) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format("Found no element \"%s\" in NodeList.", tagName));
+ } else {
+ return null;
+ }
}
var elementAsNode = elements.item(0);
if (!(elementAsNode instanceof Element)) {
@@ -108,7 +180,7 @@
public static List<Element> asElementList(NodeList nodeList) {
List<Element> elementList = new ArrayList<Element>();
for (int i = 0; i < nodeList.getLength(); i++) {
- var elementAsNode = nodeList.item(0);
+ var elementAsNode = nodeList.item(i);
if (elementAsNode instanceof Element) {
elementList.add(((Element) elementAsNode));
}
@@ -124,7 +196,7 @@
}
/** Gets the Boolean from the String value. */
- public static Boolean fromString(String s) {
+ private static Boolean fromString(String s) {
if (s == null) {
return null;
}
@@ -151,8 +223,86 @@
return ele;
}
+ /** Create an on-device Long DOM Element with the given attribute name. */
+ public static Element createOdLongEle(Document doc, String name, long l) {
+ var ele = doc.createElement(XmlUtils.OD_TAG_LONG);
+ ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+ ele.setAttribute(XmlUtils.OD_ATTR_VALUE, String.valueOf(l));
+ return ele;
+ }
+
+ /** Create an on-device Long DOM Element with the given attribute name. */
+ public static Element createOdStringEle(Document doc, String name, String val) {
+ var ele = doc.createElement(XmlUtils.OD_TAG_STRING);
+ ele.setAttribute(XmlUtils.OD_ATTR_NAME, name);
+ ele.setAttribute(XmlUtils.OD_ATTR_VALUE, val);
+ return ele;
+ }
+
+ /** Create OD style array DOM Element, which can represent any time but is stored as Strings. */
+ public static Element createOdArray(
+ Document doc, String arrayTag, String arrayName, List<String> arrayVals) {
+ Element arrEle = doc.createElement(arrayTag);
+ arrEle.setAttribute(XmlUtils.OD_ATTR_NAME, arrayName);
+ arrEle.setAttribute(XmlUtils.OD_ATTR_NUM, String.valueOf(arrayVals.size()));
+ for (String s : arrayVals) {
+ Element itemEle = doc.createElement(XmlUtils.OD_TAG_ITEM);
+ itemEle.setAttribute(XmlUtils.OD_ATTR_VALUE, s);
+ arrEle.appendChild(itemEle);
+ }
+ return arrEle;
+ }
+
/** Returns whether the String is null or empty. */
public static boolean isNullOrEmpty(String s) {
return s == null || s.isEmpty();
}
+
+ /** Tries getting required version attribute and throws exception if it doesn't exist */
+ public static Long tryGetVersion(Element ele) {
+ long version;
+ try {
+ version = Long.parseLong(ele.getAttribute(XmlUtils.HR_ATTR_VERSION));
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Malformed or missing required version in: %s", ele.getTagName()));
+ }
+ return version;
+ }
+
+ /** Gets an optional Boolean attribute. */
+ public static Boolean getBoolAttr(Element ele, String attrName) {
+ return XmlUtils.fromString(ele.getAttribute(attrName));
+ }
+
+ /** Gets a required String attribute. */
+ public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException {
+ return getStringAttr(ele, attrName, true);
+ }
+
+ /** Gets a String attribute; throws exception if required and non-existent. */
+ public static String getStringAttr(Element ele, String attrName, boolean required)
+ throws MalformedXmlException {
+ String s = ele.getAttribute(attrName);
+ if (isNullOrEmpty(s)) {
+ if (required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Malformed or missing required %s in: %s",
+ attrName, ele.getTagName()));
+ } else {
+ return null;
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Utility method for making a List from one element, to support easier refactoring if needed.
+ * For example, List.of() doesn't support null elements.
+ */
+ public static List<Element> listOf(Element e) {
+ return Arrays.asList(e);
+ }
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java
new file mode 100644
index 0000000..7d54215
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/AslgenUtil.java
@@ -0,0 +1,26 @@
+/*
+ * 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.asllib.util;
+
+public class AslgenUtil {
+ private static final String ASLGEN_TAG = "ASLGEN";
+
+ /** Log info. */
+ public static void logI(String s) {
+ System.out.println(String.format("%s -- INFO: %s", ASLGEN_TAG, s));
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java
new file mode 100644
index 0000000..7ebb7a1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.aslgen;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ AslgenTests.class,
+})
+public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
new file mode 100644
index 0000000..3026f8b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.aslgen;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.asllib.AndroidSafetyLabel;
+import com.android.asllib.AslConverter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+@RunWith(JUnit4.class)
+public class AslgenTests {
+ private static final String VALID_MAPPINGS_PATH = "com/android/aslgen/validmappings";
+ private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
+ private static final String HR_XML_FILENAME = "hr.xml";
+ private static final String OD_XML_FILENAME = "od.xml";
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ /** Tests valid mappings between HR and OD. */
+ @Test
+ public void testValidMappings() throws Exception {
+ System.out.println("start testing valid mappings.");
+
+ for (String subdir : VALID_MAPPINGS_SUBDIRS) {
+ Path hrPath = Paths.get(VALID_MAPPINGS_PATH, subdir, HR_XML_FILENAME);
+ Path odPath = Paths.get(VALID_MAPPINGS_PATH, subdir, OD_XML_FILENAME);
+
+ System.out.println("hr path: " + hrPath.toString());
+ System.out.println("od path: " + odPath.toString());
+
+ InputStream hrStream =
+ getClass().getClassLoader().getResourceAsStream(hrPath.toString());
+ String hrContents = new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
+ InputStream odStream =
+ getClass().getClassLoader().getResourceAsStream(odPath.toString());
+ String odContents = new String(odStream.readAllBytes(), StandardCharsets.UTF_8);
+ AndroidSafetyLabel asl =
+ AslConverter.readFromString(hrContents, AslConverter.Format.HUMAN_READABLE);
+ String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
+ System.out.println("out: " + out);
+
+ assertEquals(getFormattedXml(out), getFormattedXml(odContents));
+ }
+ }
+
+ private static String getFormattedXml(String xmlStr)
+ throws ParserConfigurationException, IOException, SAXException, TransformerException {
+ InputStream stream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ StreamResult streamResult = new StreamResult(outStream); // out
+ DOMSource domSource = new DOMSource(document);
+ transformer.transform(domSource, streamResult);
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml
new file mode 100644
index 0000000..fe02055
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml
@@ -0,0 +1,11 @@
+<app-metadata-bundles>
+ <safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="contacts"
+ dataType="contacts"
+ isSharingOptional="false"
+ ephemeral="true"
+ purposes="analytics" />
+ </data-labels>
+ </safety-labels>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml
new file mode 100644
index 0000000..ba6327e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml
@@ -0,0 +1,17 @@
+<bundle>
+ <pbundle_as_map name="safety_labels">
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="contacts">
+ <pbundle_as_map name="contacts">
+ <int-array name="purposes" num="1">
+ <item value="2"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="true"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</bundle>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml
new file mode 100644
index 0000000..202cc1e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml
@@ -0,0 +1,16 @@
+<app-metadata-bundles>
+ <safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ isSharingOptional="true"
+ ephemeral="true"
+ purposes="app_functionality|analytics" />
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ isSharingOptional="false"
+ ephemeral="false"
+ purposes="app_functionality" />
+ </data-labels>
+ </safety-labels>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml
new file mode 100644
index 0000000..fbcb4fb
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml
@@ -0,0 +1,25 @@
+<bundle>
+ <pbundle_as_map name="safety_labels">
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="2">
+ <item value="2"/>
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="true"/>
+ <boolean name="ephemeral" value="true"/>
+ </pbundle_as_map>
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/test.xml b/tools/app_metadata_bundles/src/test/resources/test.xml
new file mode 100644
index 0000000..202cc1e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/test.xml
@@ -0,0 +1,16 @@
+<app-metadata-bundles>
+ <safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ isSharingOptional="true"
+ ephemeral="true"
+ purposes="app_functionality|analytics" />
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ isSharingOptional="false"
+ ephemeral="false"
+ purposes="app_functionality" />
+ </data-labels>
+ </safety-labels>
+</app-metadata-bundles>
\ No newline at end of file