diff options
284 files changed, 6003 insertions, 1148 deletions
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 d0a1b027ec48..154b2d763af8 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 @@ public class AlarmManagerService extends SystemService { // 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 @@ public class AlarmManagerService extends SystemService { @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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { + ", 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { 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 000000000000..a0d9133b93da --- /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 b897c1acd17f..88a3c6f97eb6 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 af7b27e51e20..ac240ccff017 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.Binder; 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { 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 ee9400fb8408..852b00b38347 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 @@ public final class FlexibilityController extends StateController { @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 @@ public final class FlexibilityController extends StateController { } } + /** + * 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 @@ public final class FlexibilityController extends StateController { @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 b8c2d90e23c6..1cfc02531ebc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9242,140 +9242,140 @@ package android.app.people { 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"; - } - - 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); - } - - 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"; - } - - 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"; - } - - 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(); - } - - 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"; - } - - 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 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"; + } + + @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); + } + + @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"; + } + + @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"; + } + + @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(); + } + + @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"; + } + + @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 @@ package android.graphics.fonts { 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/system-current.txt b/core/api/system-current.txt index 5547028e489f..501203efc2ef 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2186,7 +2186,7 @@ package android.app.contextualsearch { field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR; } - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager { + @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchManager { method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int); field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a73aa71a75b0..ca5d3eb4a292 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3977,6 +3977,7 @@ package android.view.inputmethod { 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 5282aa3fccf7..7334676c905d 100644 --- a/core/java/android/accessibilityservice/BrailleDisplayController.java +++ b/core/java/android/accessibilityservice/BrailleDisplayController.java @@ -305,4 +305,6 @@ public interface BrailleDisplayController { @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 5843e51bf0c9..e66f7fe195e6 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -148,6 +148,13 @@ public abstract class ActivityManagerInternal { 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 41b97d0ad5d2..97c2e43a1db6 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -42,6 +42,7 @@ per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS # 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 558280396348..fe37c0ccf5de 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 b1e7b628baa2..370aff8e7a0f 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -120,6 +120,17 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } } - redirectResourcesToNewImplLocked(updatedResourceKeys); + redirectAllResourcesToNewImplLocked(updatedResourceKeys); } } @@ -1707,6 +1718,43 @@ public class ResourcesManager { } } + // 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 class ResourcesManager { 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/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java index 0bbd1e546e5d..378193f8834c 100644 --- a/core/java/android/app/contextualsearch/CallbackToken.java +++ b/core/java/android/app/contextualsearch/CallbackToken.java @@ -51,6 +51,7 @@ public final class CallbackToken implements Parcelable { private static final String TAG = CallbackToken.class.getSimpleName(); private final IBinder mToken; + private final Object mLock = new Object(); private boolean mTokenUsed = false; public CallbackToken() { @@ -75,10 +76,14 @@ public final class CallbackToken implements Parcelable { public void getContextualSearchState(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) { if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + mToken); - if (mTokenUsed) { + boolean tokenUsed; + synchronized (mLock) { + tokenUsed = markUsedLocked(); + } + if (tokenUsed) { callback.onError(new IllegalAccessException("Token already used.")); + return; } - mTokenUsed = true; try { // Get the service from the system server. IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE); @@ -96,6 +101,12 @@ public final class CallbackToken implements Parcelable { } } + private boolean markUsedLocked() { + boolean oldValue = mTokenUsed; + mTokenUsed = true; + return oldValue; + } + /** * Return the token necessary for validating the caller of {@link #getContextualSearchState}. * diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index a894a0e27b95..c080a6b423c5 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -43,7 +43,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_SERVICE) -public class ContextualSearchManager { +public final class ContextualSearchManager { /** * Key to get the entrypoint from the extras of the activity launched by contextual search. diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 475ee7a1ce3d..5514868a32f5 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -41,7 +41,12 @@ import java.util.Objects; * * <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 @@ public final class Slice implements Parcelable { /** * 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 2d6f4a6fd227..27c726dac4e5 100644 --- a/core/java/android/app/slice/SliceItem.java +++ b/core/java/android/app/slice/SliceItem.java @@ -53,7 +53,12 @@ import java.util.List; * 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 2e179d063b76..4fc2a0ce2286 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -59,7 +59,12 @@ import java.util.Set; * 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 746beaf939d9..abfe3a2fdc75 100644 --- a/core/java/android/app/slice/SliceMetrics.java +++ b/core/java/android/app/slice/SliceMetrics.java @@ -31,7 +31,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; * 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 42c3aa6aa0ea..4374550c3933 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -91,7 +91,12 @@ import java.util.Set; * </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 a33234981059..078f552bb681 100644 --- a/core/java/android/app/slice/SliceSpec.java +++ b/core/java/android/app/slice/SliceSpec.java @@ -38,7 +38,12 @@ import android.os.Parcelable; * * @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/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bff90f1d2298..5d4babb8a36d 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -847,5 +847,5 @@ interface IPackageManager { @EnforcePermission("GET_APP_METADATA") int getAppMetadataSource(String packageName, int userId); - ComponentName getDomainVerificationAgent(); + ComponentName getDomainVerificationAgent(int userId); } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 1f5f88f51d55..a720b6473be5 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -339,14 +339,17 @@ public class Resources { 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 4c22fee332e6..31cacb705d0e 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -228,6 +228,11 @@ public class ResourcesImpl { 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 @@ public class ResourcesImpl { 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 4dbdd91d5fc7..2cd7aeb2d99d 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 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - registerCompatOnBackInvokedCallback(); + registerDefaultOnBackInvokedCallback(); } @@ -3107,18 +3108,27 @@ public class InputMethodService extends AbstractInputMethodService { * 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 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); - unregisterCompatOnBackInvokedCallback(); + unregisterDefaultOnBackInvokedCallback(); } /** @@ -3328,7 +3338,7 @@ public class InputMethodService extends AbstractInputMethodService { // 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 @@ public class InputMethodService extends AbstractInputMethodService { 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 c74d50a004bf..363e252b8f27 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1002,6 +1002,21 @@ public final class Settings { "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/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index ca2e56d383e5..2e39f73f6233 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -29,6 +29,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver { private Choreographer mChoreographer; private boolean mBatchingEnabled; private boolean mBatchedInputScheduled; + private final String mTag; private final Handler mHandler; private final Runnable mConsumeBatchedInputEvents = new Runnable() { @Override @@ -43,6 +44,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver { super(inputChannel, looper); mChoreographer = choreographer; mBatchingEnabled = true; + mTag = inputChannel.getName(); traceBoolVariable("mBatchingEnabled", mBatchingEnabled); traceBoolVariable("mBatchedInputScheduled", mBatchedInputScheduled); mHandler = new Handler(looper); @@ -123,7 +125,12 @@ public class BatchedInputEventReceiver extends InputEventReceiver { private final class BatchedInputRunnable implements Runnable { @Override public void run() { - doConsumeBatchedInput(mChoreographer.getFrameTimeNanos()); + try { + Trace.traceBegin(Trace.TRACE_TAG_INPUT, mTag); + doConsumeBatchedInput(mChoreographer.getFrameTimeNanos()); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } } } private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable(); diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java new file mode 100644 index 000000000000..d14e858d9fa1 --- /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 2fcffd06db62..90aafbdb164d 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.all; 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 class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, - interpolator, animationType, getLayoutInsetsDuringAnimationMode(types), + interpolator, animationType, + getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack), false /* useInsetsAnimationThread */, null /* statsToken */); } @@ -1526,7 +1530,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 dd548c6f9da8..0d9e471e77af 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -110,10 +110,12 @@ import static android.view.accessibility.Flags.reduceWindowContentChangedEventTh 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 @@ public final class ViewRootImpl implements ViewParent, new InputEventConsistencyVerifier(this, 0) : null; private final InsetsController mInsetsController; + private final ImeBackAnimationController mImeBackAnimationController; private final ImeFocusController mImeFocusController; private boolean mIsSurfaceOpaque; @@ -1148,6 +1151,7 @@ public final class ViewRootImpl implements ViewParent, private String mLargestViewTraceName; private static boolean sToolkitSetFrameRateReadOnlyFlagValue; + private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; private static boolean sToolkitFrameRateTypingReadOnlyFlagValue; @@ -1155,6 +1159,8 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, // 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 @@ public final class ViewRootImpl implements ViewParent, == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } - @VisibleForTesting + @VisibleForTesting(visibility = PACKAGE) public InsetsController getInsetsController() { return mInsetsController; } @@ -10166,13 +10173,18 @@ public final class ViewRootImpl implements ViewParent, final class ConsumeBatchedInputRunnable implements Runnable { @Override public void run() { - mConsumeBatchedInputScheduled = false; - if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) { - // If we consumed a batch here, we want to go ahead and schedule the - // consumption of batched input events on the next frame. Otherwise, we would - // wait until we have more input events pending and might get starved by other - // things occurring in the process. - scheduleConsumeBatchedInput(); + Trace.traceBegin(TRACE_TAG_VIEW, mTag); + try { + mConsumeBatchedInputScheduled = false; + if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) { + // If we consumed a batch here, we want to go ahead and schedule the + // consumption of batched input events on the next frame. Otherwise, we would + // wait until we have more input events pending and might get starved by other + // things occurring in the process. + scheduleConsumeBatchedInput(); + } + } finally { + Trace.traceEnd(TRACE_TAG_VIEW); } } } @@ -12144,7 +12156,8 @@ public final class ViewRootImpl implements ViewParent, + "IWindow:%s Session:%s", mOnBackInvokedDispatcher, mBasePackageName, mWindow, mWindowSession)); } - mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow); + mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow, + mImeBackAnimationController); } private void sendBackKeyEvent(int action) { @@ -12788,7 +12801,9 @@ public final class ViewRootImpl implements ViewParent, 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 06598b3dfdbd..1d4d18b120c9 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 @@ flag { 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 cedf8d04ed99..f454a6abf6a0 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -770,6 +770,20 @@ final class IInputMethodManagerGlobalInvoker { } @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 8efb201d08d6..80b23969ed73 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -4316,6 +4316,19 @@ public final class InputMethodManager { } /** + * 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 f54ef3868f53..ab6b5122ea05 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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te 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 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te 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 0cc9a0d75154..da1993d9251c 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -148,8 +148,17 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc @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 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } /** + * 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 45d7767380a1..bcbac9319887 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; +import android.view.ImeBackAnimationController; import androidx.annotation.VisibleForTesting; @@ -71,6 +72,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @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 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * 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 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { clear(); mWindow = null; mWindowSession = null; + mImeBackAnimationController = null; } // TODO: Take an Executor for the callback to run on. @@ -125,6 +132,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { 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 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { 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 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } else { Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress - + "callback=" + callback); + + " callback=" + callback); } } @@ -243,9 +256,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { 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 7fbec67ec4e9..fa0dab09a8b3 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 @@ flag { 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 b45bc1c46967..ab4edb65780b 100644 --- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -86,4 +86,13 @@ interface IImeTracker { @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 1b18485e35fa..f8eb5be641e0 100644 --- a/core/java/com/android/internal/net/ConnectivityBlobStore.java +++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java @@ -19,6 +19,7 @@ package com.android.internal.net; 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 @@ public class ConnectivityBlobStore { 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 e14c99b61e2f..1517147f84d6 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 62d58b65e62a..c05ea3d65562 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 000000000000..85b4f11fb9ad --- /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 0c1e8793bfc9..6b3cf7bb628f 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 @@ public class ResourcesManagerTest extends TestCase { 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 000000000000..a617fa433a53 --- /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 000000000000..ceea6caf410b --- /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 a709d7be898b..6321e5d97589 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -96,7 +96,7 @@ public class WindowOnBackInvokedDispatcherTest { 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 68545cfe889c..ad4ccc9492f6 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 @@ public class ConnectivityBlobStoreTest { 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 e441c531a4c9..199e9293ad65 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -120,24 +120,6 @@ public final class FontFamily { } /** - * 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 @@ public final class FontFamily { * 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 9b14ce467662..b749a06bd516 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -165,15 +165,27 @@ java_library { }, } +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 index 72fba3bb7de4..8481c446c6aa 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 index 7f4a8f1d476a..526407e25d98 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 index da39017a0313..5e49f559ca64 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/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.java new file mode 100644 index 000000000000..a1496ac1d33b --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ChoreographerSfVsync.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 com.android.wm.shell.shared.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 {} 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 index 9ac7a12bc509..52a717b3a60c 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/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java new file mode 100644 index 000000000000..ae5188cf8093 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ExternalThread.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.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 {} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java new file mode 100644 index 000000000000..bd2887e39ef1 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellAnimationThread.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.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 {} 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 index 4cd3c903f2f8..586ac8297e26 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/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java new file mode 100644 index 000000000000..6c879a491fe0 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.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 {} 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 index c2fd54fd96d7..4887dbe81b25 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 8d8dc10951a6..26432111efdc 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.KeyEvent; 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 9b9798c6d93b..ad3be3d0b022 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.animation.FlingAnimationUtils; 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 7561a266c5ec..3253cac23ce8 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.internal.protolog.common.ProtoLog 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 cfd9fb613414..cae2e80d5320 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.policy.ScreenDecorationsUtils; 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 fcf500a60166..e33aa7568d09 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.dynamicanimation.animation.SpringForce; 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 4455a3caa7d9..ce8a460f10d9 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.SingleInstanceRemoteListener; 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 26077cf7057b..127a49fc7875 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 android.window.ScreenCapture.SynchronousScreenCaptureListener; 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 b828aac39040..2873d58439cd 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 android.window.WindowContainerTransaction; 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 8353900be0ef..dcbc72ab0d32 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 android.window.WindowContainerTransaction; 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 ca06024a9adb..55dc793cc3b6 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 android.view.inputmethod.ImeTracker; 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 53683c67d825..43c92cab6a68 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 android.view.Surface; 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 4009ad21b9b8..000000000000 --- 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 7560f71d1f98..000000000000 --- 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 0479f8780c79..000000000000 --- 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 423f4ce3bfd4..000000000000 --- 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 317e48e19c13..c421dec025f2 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 androidx.annotation.Nullable; 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 fa2e23647a39..cf3ad4299cea 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 android.provider.DeviceConfig; 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 216da070754b..011093718671 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.ShellExecutor; 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 512211460753..73228de83c0f 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.SystemWindows; 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.performance.PerfHintController; 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.TaskViewFactory; 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 0cc545a7724a..c5644a8f6876 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 androidx.annotation.Nullable; 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 04f0f44d2876..b933e5d70586 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.ShellExecutor; 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.onehanded.OneHandedController; 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 1e3d7fb06da2..d644006cde81 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.SyncTransactionQueue; 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.PhonePipMenuController; 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 458ea05e620d..ae07812a2427 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.DisplayController; 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.PhonePipMenuController; 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 54c2aeab4976..8d1b15c1e631 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.ShellExecutor; 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.TvPipMenuController; 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 5889da12d6e9..df1b06225fda 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 @@ package com.android.wm.shell.desktopmode; 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 1b1c96764e88..c369061810d0 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.RemoteCallable 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.draganddrop.DragAndDropController 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 7da1b23dd5b1..165feec58455 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.DisplayController; 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 73de231fb63a..863a51ad575b 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 android.window.WindowContainerTransaction; 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 33c299f0b161..4215b2cc5f29 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 @@ package com.android.wm.shell.keyguard; 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 2ee334873780..b000e3228b9a 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 @@ package com.android.wm.shell.onehanded; 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 679d4ca2ac48..39b9000856f2 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.RemoteCallable; 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 a9aa6badcfe2..7b1ef5c6cddd 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 @@ package com.android.wm.shell.pip; 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 bd186ba22588..cdeb00b7b9e1 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.DisplayController; 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.PipUiEventLogger; 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 2616b8b08bf1..eebd13370321 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 370720746808..f9fcfacf47f9 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.ShellExecutor; 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 2b433e9c4227..576219769e61 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.annotation.NonNull; 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 3e34c303e161..c3261bbc7351 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.ShellExecutor; 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.DragAndDropController; 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 4465aef0258d..3353c7bd81c2 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.internal.protolog.common.ProtoLog; 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 2f6edc226c45..5ced1fb41a41 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; 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 a7e4b0119480..f0a2315d7deb 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 @@ package com.android.wm.shell.taskview; 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 7eed5883043d..e4fcff0c372a 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 android.content.Context; 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 cb2944c120e0..c9185ae39114 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 android.window.TransitionInfo; 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 a77602b3d2d0..437a00e4a160 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.ExternalInterfaceBinder; 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 66efa02de764..e7d37addb368 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.TestShellExecutor; 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 5b4ab5f2d3b1..efa9b1174a3a 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::reparentSurfaceControl(ASurfaceControl* parent) { } 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 @@ void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* t } } +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 1bf2c1f9c4ef..2d77dd8d09bc 100644 --- a/libs/hwui/WebViewFunctorManager.h +++ b/libs/hwui/WebViewFunctorManager.h @@ -60,6 +60,10 @@ public: void onRemovedFromTree() { mReference.onRemovedFromTree(); } + const std::vector<int32_t>& getRenderingThreads() const { + return mReference.getRenderingThreads(); + } + private: friend class WebViewFunctor; @@ -82,6 +86,7 @@ public: 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 @@ private: bool mCreatedHandle = false; int32_t mParentSurfaceControlGenerationId = 0; ASurfaceControl* mSurfaceControl = nullptr; + std::vector<int32_t> mRenderingThreads; }; class WebViewFunctorManager { @@ -113,6 +119,7 @@ public: 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 abf64d099935..1fbd580874f6 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -777,6 +777,8 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { (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 2362331aca26..6993d5240187 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 @@ void HintSessionWrapper::HintSessionBinding::init() { BIND_APH_METHOD(updateTargetWorkDuration); BIND_APH_METHOD(reportActualWorkDuration); BIND_APH_METHOD(sendHint); + BIND_APH_METHOD(setThreads); mInitialized = true; } @@ -67,6 +69,10 @@ void HintSessionWrapper::destroy() { 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 @@ bool HintSessionWrapper::init() { 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 @@ void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) { 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 41891cd80a42..14e7a53fd94f 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 @@ public: 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 @@ private: pid_t mUiThreadId; pid_t mRenderThreadId; + std::vector<pid_t> mPermanentSessionTids; + std::vector<pid_t> mActiveFunctorTids; bool mSessionValid = true; @@ -82,6 +89,8 @@ private: 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 10a740a1f803..c16602c29e2a 100644 --- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp +++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp @@ -58,6 +58,7 @@ protected: 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 @@ protected: 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 @@ void HintSessionWrapperTests::SetUp() { 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 @@ void HintSessionWrapperTests::MockHintSessionBinding::init() { sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration; sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration; sMockBinding->sendHint = &stubSendHint; + sMockBinding->setThreads = &stubSetThreads; } void HintSessionWrapperTests::TearDown() { @@ -339,4 +351,44 @@ TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct 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 1905fa8ce612..82d43bcaaec9 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -65,6 +65,7 @@ import java.util.concurrent.BlockingQueue; 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 @@ final public class MediaCodec { } } + // 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 @@ final public class MediaCodec { } // 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 abad46046890..8ff4305a9817 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_HLG_EDITING; 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 @@ public final class MediaCodecInfo { 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 @@ public final class MediaCodecInfo { 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 d6d74e8a087a..94fce797f5d6 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -122,9 +122,6 @@ cc_library_shared { "-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 7caa9e4863f9..cf5059ceb3c9 100644 --- a/media/jni/audioeffect/Android.bp +++ b/media/jni/audioeffect/Android.bp @@ -44,7 +44,4 @@ cc_library_shared { "-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 976a3ad69901..bcc737a351a9 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.PackageManager; 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.NonNull; 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 @@ public class PackageUtil { 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 @@ public class PackageUtil { 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 @@ public class PackageUtil { @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 @@ public class PackageUtil { 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 000000000000..c7fbb5f0374b --- /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 000000000000..a2b964882c7c --- /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 000000000000..fff41c3583f2 --- /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 2889ce26d65d..56118dae3f96 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.SystemClock; 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 @@ public class ApplicationsState { 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 class ApplicationsState { */ 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 @@ public class ApplicationsState { 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 @@ public class ApplicationsState { 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 @@ public class ApplicationsState { } 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 class ApplicationsState { } }; + 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 e7fec692bd63..65a5317ed0cb 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 @@ interface AudioRepository { 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 @@ class AudioRepositoryImpl( ) } + 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 778653b9bd44..33f917e701c2 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 @@ class AudioVolumeInteractor( 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 @@ class AudioVolumeInteractor( } } - 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 fe83ffb094e2..b9748883b25d 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 @@ public class ApplicationsStateTest { } @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 dbdf970467b5..24cc8a4cfcec 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 @@ private class AnimatedDialog( 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 dd32851a97cf..4d327e1d8beb 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 @@ fun interface BackAnimationSpec { /** 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 @@ fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( translateYEasing: Interpolator = Interpolators.LINEAR, scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE, ): BackAnimationSpec { - val screenWidthPx = displayMetrics.widthPixels - val screenHeightPx = displayMetrics.heightPixels + 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 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 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 b8d446961a88..b05729669f7c 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 @@ import android.util.DisplayMetrics * 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 @@ fun BackAnimationSpec.Companion.dismissAppForSysUi( * 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 @@ fun BackAnimationSpec.Companion.crossTaskForSysUi( * 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 @@ fun BackAnimationSpec.Companion.innerAreaDismissForSysUi( * 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 0f63548b6f0c..9dd23289d8b5 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 @@ fun Dialog.registerAnimationOnBackInvoked( targetView: View, backAnimationSpec: BackAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi( - displayMetrics = targetView.resources.displayMetrics, + displayMetricsProvider = { targetView.resources.displayMetrics }, ), ) { targetView.registerOnBackInvokedCallbackOnViewAttached( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index a1d8c29c2a39..bdd888f45182 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -8,12 +8,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout @@ -21,12 +24,13 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions -import com.android.compose.animation.scene.updateSceneTransitionLayoutState import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.SceneDataSourceDelegator +import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource import com.android.systemui.statusbar.phone.SystemUIDialogFactory object Communal { @@ -63,27 +67,35 @@ val sceneTransitions = transitions { fun CommunalContainer( modifier: Modifier = Modifier, viewModel: CommunalViewModel, + dataSourceDelegator: SceneDataSourceDelegator, dialogFactory: SystemUIDialogFactory, ) { - val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) - val sceneTransitionLayoutState = - updateSceneTransitionLayoutState( - currentScene, - onChangeScene = { viewModel.onSceneChanged(it) }, + val coroutineScope = rememberCoroutineScope() + val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) + val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) + val state: MutableSceneTransitionLayoutState = remember { + MutableSceneTransitionLayoutState( + initialScene = currentSceneKey, transitions = sceneTransitions, enableInterruptions = false, ) - val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) + } + + DisposableEffect(state) { + val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope) + dataSourceDelegator.setDelegate(dataSource) + onDispose { dataSourceDelegator.setDelegate(null) } + } // This effect exposes the SceneTransitionLayout's observable transition state to the rest of // the system, and unsets it when the view is disposed to avoid a memory leak. - DisposableEffect(viewModel, sceneTransitionLayoutState) { - viewModel.setTransitionState(sceneTransitionLayoutState.observableTransitionState()) + DisposableEffect(viewModel, state) { + viewModel.setTransitionState(state.observableTransitionState()) onDispose { viewModel.setTransitionState(null) } } SceneTransitionLayout( - state = sceneTransitionLayoutState, + state = state, modifier = modifier.fillMaxSize(), swipeSourceDetector = FixedSizeEdgeDetector( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 503779c1801b..ed8027756f47 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 @@ private fun EmptyStateCta( 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 @@ private fun EmptyStateCta( 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 @@ private fun EmptyStateCta( 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/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 320c455a8201..c008a1a4d192 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -16,10 +16,15 @@ package com.android.systemui.keyguard.ui.composable.blueprint +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect @@ -28,6 +33,7 @@ import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.TopAreaSection @@ -52,6 +58,7 @@ constructor( private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val topAreaSection: TopAreaSection, + private val notificationSection: NotificationSection, ) : ComposableLockscreenSceneBlueprint { override val id: String = "default" @@ -59,6 +66,8 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible + val shouldUseSplitNotificationShade by + viewModel.shouldUseSplitNotificationShade.collectAsState() LockscreenLongPress( viewModel = viewModel.longPress, @@ -68,10 +77,27 @@ constructor( content = { // Constrained to above the lock icon. Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxSize(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(topAreaSection) { DefaultClockLayoutWithNotifications() } + + Box { + with(topAreaSection) { DefaultClockLayout() } + if (shouldUseSplitNotificationShade) { + with(notificationSection) { + Notifications( + Modifier.fillMaxWidth(0.5f) + .fillMaxHeight() + .align(alignment = Alignment.TopEnd) + ) + } + } + } + if (!shouldUseSplitNotificationShade) { + with(notificationSection) { + Notifications(Modifier.weight(weight = 1f)) + } + } if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { AmbientIndication(modifier = Modifier.fillMaxWidth()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index 64c2cb3670c8..091a43923a6e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -16,10 +16,15 @@ package com.android.systemui.keyguard.ui.composable.blueprint +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect @@ -28,6 +33,7 @@ import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.NotificationSection import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.TopAreaSection @@ -52,6 +58,7 @@ constructor( private val bottomAreaSection: BottomAreaSection, private val settingsMenuSection: SettingsMenuSection, private val topAreaSection: TopAreaSection, + private val notificationSection: NotificationSection, ) : ComposableLockscreenSceneBlueprint { override val id: String = "shortcuts-besides-udfps" @@ -59,6 +66,8 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible + val shouldUseSplitNotificationShade by + viewModel.shouldUseSplitNotificationShade.collectAsState() LockscreenLongPress( viewModel = viewModel.longPress, @@ -68,11 +77,27 @@ constructor( content = { // Constrained to above the lock icon. Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxSize(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - with(topAreaSection) { DefaultClockLayoutWithNotifications() } + Box { + with(topAreaSection) { DefaultClockLayout() } + if (shouldUseSplitNotificationShade) { + with(notificationSection) { + Notifications( + Modifier.fillMaxWidth(0.5f) + .fillMaxHeight() + .align(alignment = Alignment.TopEnd) + ) + } + } + } + if (!shouldUseSplitNotificationShade) { + with(notificationSection) { + Notifications(Modifier.weight(weight = 1f)) + } + } if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { AmbientIndication(modifier = Modifier.fillMaxWidth()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt index fe774a0d6db2..09d76a341442 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt @@ -86,6 +86,7 @@ constructor( val burnIn = rememberBurnIn(clockInteractor) val resources = LocalContext.current.resources val currentClockState = clockViewModel.currentClock.collectAsState() + val areNotificationsVisible by viewModel.areNotificationsVisible.collectAsState() LockscreenLongPress( viewModel = viewModel.longPress, modifier = modifier, @@ -145,7 +146,7 @@ constructor( with(mediaCarouselSection) { MediaCarousel() } - if (viewModel.areNotificationsVisible) { + if (areNotificationsVisible) { with(notificationSection) { Notifications( modifier = Modifier.fillMaxWidth().weight(weight = 1f) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 6b86a484069b..fa0a1c4663b1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -17,12 +17,25 @@ package com.android.systemui.keyguard.ui.composable.section import android.view.ViewGroup +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.thenIf +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint +import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.res.R +import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder @@ -39,6 +52,7 @@ constructor( sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, stackScrollLayout: NotificationStackScrollLayout, sharedNotificationContainerBinder: SharedNotificationContainerBinder, + private val lockscreenContentViewModel: LockscreenContentViewModel, ) { init { @@ -65,9 +79,27 @@ constructor( @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { + val shouldUseSplitNotificationShade by + lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState() + val areNotificationsVisible by + lockscreenContentViewModel.areNotificationsVisible.collectAsState() + val splitShadeTopMargin: Dp = + if (Flags.centralizedStatusBarHeightFix()) { + LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp + } else { + dimensionResource(id = R.dimen.large_screen_shade_header_height) + } + + if (!areNotificationsVisible) { + return + } + NotificationStack( viewModel = viewModel, - modifier = modifier, + modifier = + modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) { + Modifier.padding(top = splitShadeTopMargin) + }, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index b4472fc15ac4..f8e63411ed52 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -16,30 +16,20 @@ package com.android.systemui.keyguard.ui.composable.section -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf -import com.android.systemui.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene @@ -48,8 +38,6 @@ import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitSh import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.res.R -import com.android.systemui.shade.LargeScreenHeaderHelper import javax.inject.Inject class TopAreaSection @@ -58,19 +46,16 @@ constructor( private val clockViewModel: KeyguardClockViewModel, private val smartSpaceSection: SmartSpaceSection, private val mediaCarouselSection: MediaCarouselSection, - private val notificationSection: NotificationSection, private val clockSection: DefaultClockSection, private val clockInteractor: KeyguardClockInteractor, ) { @Composable - fun DefaultClockLayoutWithNotifications( + fun DefaultClockLayout( modifier: Modifier = Modifier, ) { - val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() val currentClockLayout by clockViewModel.currentClockLayout.collectAsState() val hasCustomPositionUpdatedAnimation by clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState() - val currentScene = when (currentClockLayout) { KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK -> @@ -81,144 +66,83 @@ constructor( KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene } - val splitShadeTopMargin: Dp = - if (Flags.centralizedStatusBarHeightFix()) { - LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp - } else { - dimensionResource(id = R.dimen.large_screen_shade_header_height) - } - val burnIn = rememberBurnIn(clockInteractor) - - LaunchedEffect(isLargeClockVisible) { - if (isLargeClockVisible) { - burnIn.onSmallClockTopChanged(null) - } - } - SceneTransitionLayout( - modifier = modifier.fillMaxSize(), + modifier = modifier, currentScene = currentScene, onChangeScene = {}, transitions = ClockTransition.defaultClockTransitions, enableInterruptions = false, ) { scene(splitShadeLargeClockScene) { - Box(modifier = Modifier.fillMaxSize()) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - ) - } - - with(clockSection) { - LargeClock( - modifier = - Modifier.fillMaxSize().thenIf( - !hasCustomPositionUpdatedAnimation - ) { - // If we do not have a custom position animation, we want - // the clock to be on one half of the screen. - Modifier.offset { - IntOffset( - x = - -clockSection - .getClockCenteringDistance() - .toInt(), - y = 0, - ) - } - } - ) - } - } - } - - Row( - modifier = Modifier.fillMaxSize(), - ) { - Spacer(modifier = Modifier.weight(weight = 1f)) - with(notificationSection) { - Notifications( - modifier = - Modifier.fillMaxHeight() - .weight(weight = 1f) - .padding(top = splitShadeTopMargin) - ) - } - } + LargeClockWithSmartSpace( + shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation + ) } scene(splitShadeSmallClockScene) { - Row( - modifier = Modifier.fillMaxSize(), - ) { - Column( - modifier = Modifier.fillMaxHeight().weight(weight = 1f), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - with(clockSection) { - SmallClock( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.wrapContentSize() - ) - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - ) - } - with(mediaCarouselSection) { MediaCarousel() } - } - with(notificationSection) { - Notifications( - modifier = - Modifier.fillMaxHeight() - .weight(weight = 1f) - .padding(top = splitShadeTopMargin) - ) - } - } + SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f)) } - scene(smallClockScene) { - Column { - with(clockSection) { - SmallClock( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmallClockTopChanged, - modifier = Modifier.wrapContentSize() - ) - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - ) - } - with(mediaCarouselSection) { MediaCarousel() } - with(notificationSection) { - Notifications(modifier = Modifier.fillMaxWidth().weight(weight = 1f)) - } - } + scene(smallClockScene) { SmallClockWithSmartSpace() } + + scene(largeClockScene) { LargeClockWithSmartSpace() } + } + } + + @Composable + private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) { + val burnIn = rememberBurnIn(clockInteractor) + + Column(modifier = modifier) { + with(clockSection) { + SmallClock( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmallClockTopChanged, + modifier = Modifier.wrapContentSize() + ) + } + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + ) } + with(mediaCarouselSection) { MediaCarousel() } + } + } - scene(largeClockScene) { - Column { - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - ) - } - with(clockSection) { LargeClock(modifier = Modifier.fillMaxSize()) } - } + @Composable + private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) { + val burnIn = rememberBurnIn(clockInteractor) + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() + + LaunchedEffect(isLargeClockVisible) { + if (isLargeClockVisible) { + burnIn.onSmallClockTopChanged(null) + } + } + + Column { + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + ) + } + with(clockSection) { + LargeClock( + modifier = + Modifier.fillMaxSize().thenIf(shouldOffSetClockToOneHalf) { + // If we do not have a custom position animation, we want + // the clock to be on one half of the screen. + Modifier.offset { + IntOffset( + x = -clockSection.getClockCenteringDistance().toInt(), + y = 0, + ) + } + } + ) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 43266bfcbc55..a944afb70f38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -88,7 +88,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -158,7 +158,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -179,7 +179,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) fakeKeyguardTransitionRepository.sendTransitionSteps( @@ -207,7 +207,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_forcesCommunal() = with(kosmos) { testScope.runTest { - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) val scene by collectLastValue(communalInteractor.desiredScene) // device is docked while on the lockscreen @@ -229,7 +229,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() = with(kosmos) { testScope.runTest { - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) val scene by collectLastValue(communalInteractor.desiredScene) // device is docked while on the lockscreen @@ -261,7 +261,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -278,7 +278,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is not dreaming and on communal. fakeKeyguardRepository.setDreaming(false) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal advanceTimeBy(SCREEN_TIMEOUT.milliseconds) @@ -293,7 +293,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -316,7 +316,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is on communal, but not dreaming. fakeKeyguardRepository.setDreaming(false) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -338,7 +338,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { testScope.runTest { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) @@ -367,7 +367,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Device is dreaming and on communal. fakeKeyguardRepository.setDreaming(true) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) assertThat(scene).isEqualTo(CommunalScenes.Communal) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 43acf3197fb1..2d78a9b9d808 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -22,36 +22,26 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope -import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class CommunalRepositoryImplTest : SysuiTestCase() { - private lateinit var underTest: CommunalRepositoryImpl private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneContainerRepository = kosmos.sceneContainerRepository - - @Before - fun setUp() { - underTest = createRepositoryImpl(false) - } - - private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { - return CommunalRepositoryImpl( - testScope.backgroundScope, - kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled }, - sceneContainerRepository, + private val underTest by lazy { + CommunalRepositoryImpl( + kosmos.applicationCoroutineScope, + kosmos.sceneDataSource, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 8e9d7690f215..e7ccde26e161 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -482,7 +482,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank) val targetScene = CommunalScenes.Communal - communalRepository.setDesiredScene(targetScene) + communalRepository.changeScene(targetScene) desiredScene = collectLastValue(underTest.desiredScene) runCurrent() assertThat(desiredScene()).isEqualTo(targetScene) @@ -493,9 +493,9 @@ class CommunalInteractorTest : SysuiTestCase() { testScope.runTest { val targetScene = CommunalScenes.Communal - underTest.onSceneChanged(targetScene) + underTest.changeScene(targetScene) - val desiredScene = collectLastValue(communalRepository.desiredScene) + val desiredScene = collectLastValue(communalRepository.currentScene) runCurrent() assertThat(desiredScene()).isEqualTo(targetScene) } @@ -508,7 +508,7 @@ class CommunalInteractorTest : SysuiTestCase() { val desiredScene by collectLastValue(underTest.desiredScene) - underTest.onSceneChanged(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal) assertThat(desiredScene).isEqualTo(CommunalScenes.Communal) kosmos.setCommunalAvailable(false) @@ -659,7 +659,7 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() assertThat(isCommunalShowing()).isEqualTo(false) - underTest.onSceneChanged(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal) isCommunalShowing = collectLastValue(underTest.isCommunalShowing) runCurrent() @@ -683,12 +683,12 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true - underTest.onSceneChanged(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal) runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false - underTest.onSceneChanged(CommunalScenes.Blank) + underTest.changeScene(CommunalScenes.Blank) runCurrent() assertThat(isCommunalShowing).isFalse() } @@ -704,7 +704,7 @@ class CommunalInteractorTest : SysuiTestCase() { assertThat(isCommunalShowing).isFalse() // Verify scene changes without the flag doesn't have any impact - underTest.onSceneChanged(CommunalScenes.Communal) + underTest.changeScene(CommunalScenes.Communal) runCurrent() assertThat(isCommunalShowing).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 50b8da62b3f0..3a23e14e2777 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -158,7 +158,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { kosmos.setCommunalAvailable(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED) } @@ -171,7 +171,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } @@ -184,13 +184,13 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { goToCommunal() communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED) } private suspend fun goToCommunal() { kosmos.setCommunalAvailable(true) - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt index 8a77ed2130a9..056a401a2644 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.android.systemui.testKosmos 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 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileAsleep_isTrue() = + fun transitionFromPowerGesture_whileAsleep_isTrue() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -97,7 +115,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileWaking_isFalse() = + fun transitionFromPowerGesture_whileWaking_isFalse() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -119,7 +137,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testTransitionFromPowerGesture_whileAwake_isFalse() = + fun transitionFromPowerGesture_whileAwake_isFalse() = testScope.runTest { powerInteractor.setAwakeForTest() transitionRepository.sendTransitionSteps( @@ -140,7 +158,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() = + fun showWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() = testScope.runTest { val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) powerInteractor.setAsleepForTest() @@ -187,7 +205,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { } @Test - fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() = + fun showWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() = testScope.runTest { val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) powerInteractor.setAwakeForTest() @@ -221,4 +239,23 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { 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 0cc0c2fb530b..78bdfb350085 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 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 @@ class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() { } 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 @@ class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() { } @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 ad2ae8b41af9..e6b30176d3ee 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.ext.junit.runners.AndroidJUnit4 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 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + fun showAllNotifications_isTrue_whenLeaveShadeOpen() = + testScope.runTest { + val showAllNotifications by + collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER)) + + sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true) + + keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED)) + keyguardTransitionRepository.sendTransitionStep(step(0.1f)) + + assertThat(showAllNotifications).isTrue() + keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(showAllNotifications).isFalse() + } + + @Test + fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() = + testScope.runTest { + val showAllNotifications by + collectLastValue(underTest.showAllNotifications(500.milliseconds, PRIMARY_BOUNCER)) + + sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false) + + keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED)) + keyguardTransitionRepository.sendTransitionStep(step(0.1f)) + + assertThat(showAllNotifications).isFalse() + keyguardTransitionRepository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(showAllNotifications).isFalse() + } + + @Test fun scrimBehindAlpha_doNotLeaveShadeOpen() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index 751ac1d8b458..e9a825721f5f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.authController +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository @@ -96,7 +97,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() { shadeRepository.setShadeMode(ShadeMode.Split) kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - assertThat(underTest.areNotificationsVisible).isTrue() + assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue() } } @Test @@ -104,7 +105,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() { with(kosmos) { testScope.runTest { kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) - assertThat(underTest.areNotificationsVisible).isTrue() + assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isTrue() } } @@ -113,7 +114,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() { with(kosmos) { testScope.runTest { kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - assertThat(underTest.areNotificationsVisible).isFalse() + assertThat(collectLastValue(underTest.areNotificationsVisible).invoke()).isFalse() } } @@ -122,7 +123,8 @@ class LockscreenContentViewModelTest : SysuiTestCase() { with(kosmos) { testScope.runTest { shadeRepository.setShadeMode(ShadeMode.Split) - assertThat(underTest.shouldUseSplitNotificationShade).isTrue() + assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke()) + .isTrue() } } @@ -131,16 +133,8 @@ class LockscreenContentViewModelTest : SysuiTestCase() { with(kosmos) { testScope.runTest { shadeRepository.setShadeMode(ShadeMode.Single) - assertThat(underTest.shouldUseSplitNotificationShade).isFalse() - } - } - - @Test - fun sceneKey() = - with(kosmos) { - testScope.runTest { - shadeRepository.setShadeMode(ShadeMode.Single) - assertThat(underTest.shouldUseSplitNotificationShade).isFalse() + assertThat(collectLastValue(underTest.shouldUseSplitNotificationShade).invoke()) + .isFalse() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt index 857b9f82f8bc..26cb4856e571 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.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.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,6 +41,7 @@ class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() { 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 @@ class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() { 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 c80835d9b2b3..efbdb7d466d1 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.power.domain.interactor.powerInteractor 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 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { 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 000000000000..c3366ad21e2a --- /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 594543b88b17..605e5c067eab 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.model.SysUiState 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 @@ class SceneContainerStartableTest : SysuiTestCase() { deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor, centralSurfaces = centralSurfaces, headsUpInteractor = kosmos.headsUpNotificationInteractor, + occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, ) } @@ -204,6 +207,28 @@ class SceneContainerStartableTest : SysuiTestCase() { } @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 5358a6dbb476..fa79e7fc9026 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 @@ class AudioVolumeInteractorTest : SysuiTestCase() { } @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() + } + } + } + + @Test + fun muteRingerStream_ringerMode_vibrate() { + with(kosmos) { + testScope.runTest { + val ringerMode by collectLastValue(audioRepository.ringerMode) + underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true) - assertThat(isMutable).isFalse() + 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 000000000000..edfbe7bed588 --- /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 000000000000..c1573a3ae5ac --- /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 000000000000..5f738c041f54 --- /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 a64c9aebec3e..9a7108a53042 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 ef1a21f2fdf6..c988b4afcf74 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 35f6a08795bc..a6f6d4dcf2f9 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 e004ee9fa157..d2efccd546b6 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 2195849118b4..b0d98e7c09d0 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 9036a35846af..ad09b466dd3e 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 751a3f8458bd..68d2eb358105 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 static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; 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 @@ public class Utilities { /** @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 8f1a5f79687c..985f6c8bc59f 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -454,6 +454,7 @@ public class LockIconViewController implements Dumpable { 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 1a9b01fd4996..7e94804908b6 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.res.R; 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 000000000000..af8149f59a16 --- /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 88fa2de186d0..70165f377a7c 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.util.SparseArray; 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 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @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 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { 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 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { } } + @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 35fe6b14ee28..c464c82cb90f 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 static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 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 android.widget.TextView; import androidx.annotation.NonNull; -import com.android.settingslib.Utils; import com.android.systemui.res.R; import java.lang.annotation.Retention; @@ -171,9 +169,9 @@ class MenuMessageView extends LinearLayout implements 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 4047623a4b2f..7cb028abba60 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.ColorCorrectionTile 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 @@ interface QSAccessibilityModule { @StringKey(FontScalingTile.TILE_SPEC) fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> + /** Inject HearingDevicesTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(HearingDevicesTile.TILE_SPEC) + fun bindHearingDevicesTile(hearingDevicesTile: HearingDevicesTile): QSTileImpl<*> + companion object { const val COLOR_CORRECTION_TILE_SPEC = "color_correction" const val COLOR_INVERSION_TILE_SPEC = "inversion" diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index ef686f91b36a..4d328d6cb13f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -72,7 +72,7 @@ constructor( .filterNotNull() // TODO(b/322787129): Also set a custom transition animation here to avoid the regular // slide-in animation when setting the scene programmatically - .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) } + .onEach { nextScene -> communalInteractor.changeScene(nextScene) } .launchIn(applicationScope) // TODO(b/322787129): re-enable once custom animations are in place @@ -129,7 +129,7 @@ constructor( .sample(keyguardInteractor.isDreaming, ::Pair) .collect { (shouldTimeout, isDreaming) -> if (isDreaming && shouldTimeout) { - communalInteractor.onSceneChanged(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Blank) } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt new file mode 100644 index 000000000000..5e41a1b8a9b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/Communal.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.dagger + +import javax.inject.Qualifier + +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Communal diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 82d943796e2a..72dcb26b089a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -23,11 +23,19 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.scene.shared.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneDataSource +import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import dagger.Binds import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineScope @Module( includes = @@ -47,4 +55,24 @@ interface CommunalModule { fun bindEditWidgetsActivityStarter( starter: EditWidgetsActivityStarterImpl ): EditWidgetsActivityStarter + + @Binds + @Communal + fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource + + companion object { + @Provides + @Communal + @SysUISingleton + fun providesCommunalSceneDataSourceDelegator( + @Application applicationScope: CoroutineScope + ): SceneDataSourceDelegator { + val config = + SceneContainerConfig( + sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal), + initialSceneKey = CommunalScenes.Blank + ) + return SceneDataSourceDelegator(applicationScope, config) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 201ce832cc41..8bfd8d91dfca 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -18,11 +18,12 @@ package com.android.systemui.communal.data.repository import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.scene.data.repository.SceneContainerRepository -import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.model.SceneDataSource import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn @@ -38,16 +38,15 @@ import kotlinx.coroutines.flow.stateIn /** Encapsulates the state of communal mode. */ interface CommunalRepository { /** - * Target scene as requested by the underlying [SceneTransitionLayout] or through - * [setDesiredScene]. + * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. */ - val desiredScene: StateFlow<SceneKey> + val currentScene: StateFlow<SceneKey> /** Exposes the transition state of the communal [SceneTransitionLayout]. */ val transitionState: StateFlow<ObservableTransitionState> /** Updates the requested scene. */ - fun setDesiredScene(desiredScene: SceneKey) + fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) /** * Updates the transition state of the hub [SceneTransitionLayout]. @@ -63,12 +62,10 @@ class CommunalRepositoryImpl @Inject constructor( @Background backgroundScope: CoroutineScope, - sceneContainerFlags: SceneContainerFlags, - sceneContainerRepository: SceneContainerRepository, + @Communal private val sceneDataSource: SceneDataSource, ) : CommunalRepository { - private val _desiredScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default) - override val desiredScene: StateFlow<SceneKey> = _desiredScene.asStateFlow() + override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) @@ -81,8 +78,8 @@ constructor( initialValue = defaultTransitionState, ) - override fun setDesiredScene(desiredScene: SceneKey) { - _desiredScene.value = desiredScene + override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { + sceneDataSource.changeScene(toScene, transitionKey) } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index ada984db9a39..86b254b910b8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -25,6 +25,7 @@ import android.os.UserManager import android.provider.Settings import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalPrefsRepository @@ -142,13 +143,12 @@ constructor( ) /** - * Target scene as requested by the underlying [SceneTransitionLayout] or through - * [onSceneChanged]. + * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. * * If [isCommunalAvailable] is false, will return [CommunalScenes.Blank] */ val desiredScene: Flow<SceneKey> = - communalRepository.desiredScene.combine(isCommunalAvailable) { scene, available -> + communalRepository.currentScene.combine(isCommunalAvailable) { scene, available -> if (available) scene else CommunalScenes.Blank } @@ -254,9 +254,12 @@ constructor( !(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank) } - /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */ - fun onSceneChanged(newScene: SceneKey) { - communalRepository.setDesiredScene(newScene) + /** + * Asks for an asynchronous scene witch to [newScene], which will use the corresponding + * installed transition or the one specified by [transitionKey], if provided. + */ + fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) { + communalRepository.changeScene(newScene, transitionKey) } fun setEditModeOpen(isOpen: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 531f19874b2a..095222a4b47a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -49,8 +49,8 @@ abstract class BaseCommunalViewModel( communalInteractor.signalUserInteraction() } - fun onSceneChanged(scene: SceneKey) { - communalInteractor.onSceneChanged(scene) + fun changeScene(scene: SceneKey) { + communalInteractor.changeScene(scene) } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 902133ddce1d..5f4b394a05b9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.launch import com.android.compose.theme.LocalAndroidColorScheme import com.android.compose.theme.PlatformTheme import com.android.internal.logging.UiEventLogger @@ -150,7 +149,7 @@ constructor( private fun onEditDone() { try { - communalViewModel.onSceneChanged(CommunalScenes.Communal) + communalViewModel.changeScene(CommunalScenes.Communal) checkNotNull(windowManagerService).lockNow(/* options */ null) finish() } catch (e: RemoteException) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 3b0c281a7057..e104166935c1 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.keyguard.KeyguardTransitions; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.sysui.ShellInterface; import com.android.wm.shell.taskview.TaskViewFactory; -import com.android.wm.shell.transition.ShellTransitions; import dagger.BindsInstance; import dagger.Subcomponent; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 19af371d1dfa..1ed4b503b43d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -49,6 +49,7 @@ import com.android.systemui.common.data.CommonDataLayerModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.SystemUser; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -89,6 +90,7 @@ import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; import com.android.systemui.recordissue.RecordIssueModule; import com.android.systemui.retail.dagger.RetailModeModule; +import com.android.systemui.scene.shared.model.SceneContainerConfig; import com.android.systemui.scene.shared.model.SceneDataSource; import com.android.systemui.scene.shared.model.SceneDataSourceDelegator; import com.android.systemui.scene.ui.view.WindowRootViewComponent; @@ -165,6 +167,8 @@ import java.util.concurrent.Executor; import javax.inject.Named; +import kotlinx.coroutines.CoroutineScope; + /** * A dagger module for injecting components of System UI that are required by System UI. * @@ -402,6 +406,13 @@ public abstract class SystemUIModule { @ClassKey(SystemUISecondaryUserService.class) abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service); + @Provides + @SysUISingleton + static SceneDataSourceDelegator providesSceneDataSourceDelegator( + @Application CoroutineScope applicationScope, SceneContainerConfig config) { + return new SceneDataSourceDelegator(applicationScope, config); + } + @Binds abstract SceneDataSource bindSceneDataSource(SceneDataSourceDelegator delegator); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index dfec771ced26..e04a0e542855 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 androidx.annotation.Nullable; 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.keyguard.KeyguardTransitions; 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/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 1b832d4ab98d..037c23b579c3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -57,7 +57,7 @@ constructor( !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) if (showGlanceableHub) { toGlanceableHubTransitionViewModel.startTransition() - communalInteractor.onSceneChanged(CommunalScenes.Communal) + communalInteractor.changeScene(CommunalScenes.Communal) } else { toLockscreenTransitionViewModel.startTransition() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 3134e35a92e3..6b53f4ed554f 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.domain.interactor.PowerInteractor; 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 53c81e537708..cf8358203b6c 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.Animator 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.RemoteAnimationTarget 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 @@ const val SURFACE_BEHIND_FADE_OUT_START_DELAY_MS = 0L */ @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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( // 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 9aa2202b4100..03ed5675a77a 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 @@ package com.android.systemui.keyguard.domain.interactor 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 @@ import kotlinx.coroutines.flow.stateIn 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 @@ constructor( // 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 4c846e424f4b..29041d1665c3 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.deviceentry.shared.DeviceEntryUdfpsRefactor 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 @@ constructor( 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 ac2713d88f39..8c6be989d8d9 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.KeyguardState.ALTERNATE_BOUNCE 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 @@ class AlternateBouncerToGoneTransitionViewModel constructor( bouncerToGoneFlows: BouncerToGoneFlows, animationFlow: KeyguardTransitionAnimationFlow, + private val statusBarStateController: SysuiStatusBarStateController, ) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow.setup( @@ -59,6 +61,30 @@ constructor( ) } + 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 924fc5d0333f..fe88b8169c89 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 javax.inject.Inject 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 @@ constructor( } } + /** + * 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/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt index 1f80441492bc..36896f916e7d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -18,8 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.res.Resources import com.android.keyguard.KeyguardClockSwitch +import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.res.R @@ -29,6 +31,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -42,6 +45,7 @@ constructor( private val authController: AuthController, val longPress: KeyguardLongPressViewModel, val shadeInteractor: ShadeInteractor, + @Application private val applicationScope: CoroutineScope, ) { private val clockSize = clockInteractor.clockSize @@ -50,11 +54,26 @@ constructor( val isLargeClockVisible: Boolean get() = clockSize.value == KeyguardClockSwitch.LARGE - val areNotificationsVisible: Boolean - get() = !isLargeClockVisible || shouldUseSplitNotificationShade + val shouldUseSplitNotificationShade: StateFlow<Boolean> = + shadeInteractor.shadeMode + .map { it == ShadeMode.Split } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) - val shouldUseSplitNotificationShade: Boolean - get() = shadeInteractor.shadeMode.value == ShadeMode.Split + val areNotificationsVisible: StateFlow<Boolean> = + combine(clockSize, shouldUseSplitNotificationShade) { + clockSize, + shouldUseSplitNotificationShade -> + clockSize == SMALL || shouldUseSplitNotificationShade + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) fun getSmartSpacePaddingTop(resources: Resources): Int { return if (isLargeClockVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt index 4e6aa030d993..f03625eda9b5 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.shared.model.KeyguardState 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 @@ class LockscreenToGoneTransitionViewModel @Inject constructor( animationFlow: KeyguardTransitionAnimationFlow, + private val statusBarStateController: SysuiStatusBarStateController, ) : DeviceEntryIconTransition { private val transitionAnimation: FlowBuilder = @@ -54,6 +56,26 @@ constructor( 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 53f448826e80..05878265dd6d 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 @@ constructor( 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 15b8cfb3834d..f34389ec8cb5 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 @@ object SubtitleArrayMapping { 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 000000000000..1fb701e95b85 --- /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 000000000000..a82391643e11 --- /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 42b41f88e155..0e4049bbd21e 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.model.updateFlags 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 @@ constructor( private val deviceProvisioningInteractor: DeviceProvisioningInteractor, private val centralSurfaces: CentralSurfaces, private val headsUpInteractor: HeadsUpNotificationInteractor, + private val occlusionInteractor: SceneContainerOcclusionInteractor, ) : CoreStartable { override fun start() { @@ -130,32 +132,36 @@ constructor( .distinctUntilChanged() .flatMapLatest { isAllowedToBeVisible -> if (isAllowedToBeVisible) { - sceneInteractor.transitionState - .mapNotNull { state -> - when (state) { - is ObservableTransitionState.Idle -> { - if (state.scene != Scenes.Gone) { - true to "scene is not Gone" - } else { - false to "scene is Gone" + combine( + sceneInteractor.transitionState.mapNotNull { state -> + when (state) { + is ObservableTransitionState.Idle -> { + if (state.scene != Scenes.Gone) { + true to "scene is not Gone" + } else { + false to "scene is Gone" + } } - } - is ObservableTransitionState.Transition -> { - if (state.fromScene == Scenes.Gone) { - true to "scene transitioning away from Gone" - } else { - null + is ObservableTransitionState.Transition -> { + if (state.fromScene == Scenes.Gone) { + true to "scene transitioning away from Gone" + } else { + null + } } } - } - } - .combine(headsUpInteractor.isHeadsUpOrAnimatingAway) { + }, + headsUpInteractor.isHeadsUpOrAnimatingAway, + occlusionInteractor.invisibleDueToOcclusion, + ) { visibilityForTransitionState, - isHeadsUpOrAnimatingAway -> - if (isHeadsUpOrAnimatingAway) { - true to "showing a HUN" - } else { - visibilityForTransitionState + isHeadsUpOrAnimatingAway, + invisibleDueToOcclusion, + -> + when { + isHeadsUpOrAnimatingAway -> true to "showing a HUN" + invisibleDueToOcclusion -> false to "invisible due to occlusion" + else -> visibilityForTransitionState } } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt index 69dce83b7136..2fbcba977a91 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt @@ -20,9 +20,6 @@ package com.android.systemui.scene.shared.model import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -36,14 +33,10 @@ import kotlinx.coroutines.flow.stateIn * Delegates calls to a runtime-provided [SceneDataSource] or to a no-op implementation if a * delegate isn't set. */ -@SysUISingleton -class SceneDataSourceDelegator -@Inject -constructor( - @Application private val applicationScope: CoroutineScope, +class SceneDataSourceDelegator( + applicationScope: CoroutineScope, config: SceneContainerConfig, ) : SceneDataSource { - private val noOpDelegate = NoOpSceneDataSource(config.initialSceneKey) private val delegateMutable = MutableStateFlow<SceneDataSource>(noOpDelegate) @@ -82,6 +75,7 @@ constructor( ) : SceneDataSource { override val currentScene: StateFlow<SceneKey> = MutableStateFlow(initialSceneKey).asStateFlow() + override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index b796a206b5b4..047ecb42287b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -29,8 +29,6 @@ import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; -import static java.util.Objects.requireNonNull; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.MainThread; @@ -41,7 +39,6 @@ import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; import android.app.ICompatCameraControlCallback; import android.app.Notification; -import android.app.assist.AssistContent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -73,7 +70,6 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import android.window.WindowContext; @@ -81,11 +77,11 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.screenshot.scroll.LongScreenshotActivity; @@ -218,17 +214,10 @@ public class ScreenshotController { // ScreenshotNotificationSmartActionsProvider. static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; static final String EXTRA_ID = "android:screenshot_id"; - static final String ACTION_TYPE_DELETE = "Delete"; - static final String ACTION_TYPE_SHARE = "Share"; - static final String ACTION_TYPE_EDIT = "Edit"; static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; - static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition"; static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin"; - static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; - static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; - static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; // From WizardManagerHelper.java private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; @@ -247,10 +236,10 @@ public class ScreenshotController { private final Executor mMainExecutor; private final ExecutorService mBgExecutor; private final BroadcastSender mBroadcastSender; + private final BroadcastDispatcher mBroadcastDispatcher; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; - private final AccessibilityManager mAccessibilityManager; @Nullable private final ScreenshotSoundController mScreenshotSoundController; private final ScrollCaptureClient mScrollCaptureClient; @@ -278,7 +267,7 @@ public class ScreenshotController { private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; private String mPackageName = ""; - private BroadcastReceiver mCopyBroadcastReceiver; + private final BroadcastReceiver mCopyBroadcastReceiver; // When false, the screenshot is taken without showing the ui. Note that this only applies to // external displays, as on the default one the UI should **always** be shown. @@ -300,6 +289,8 @@ public class ScreenshotController { @AssistedInject ScreenshotController( Context context, + DisplayManager displayManager, + WindowManager windowManager, FeatureFlags flags, ScreenshotViewProxy.Factory viewProxyFactory, ScreenshotActionsProvider.Factory actionsProviderFactory, @@ -315,6 +306,7 @@ public class ScreenshotController { ActivityManager activityManager, TimeoutHandler timeoutHandler, BroadcastSender broadcastSender, + BroadcastDispatcher broadcastDispatcher, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, UserManager userManager, @@ -337,16 +329,17 @@ public class ScreenshotController { mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider; mBgExecutor = Executors.newSingleThreadExecutor(); mBroadcastSender = broadcastSender; + mBroadcastDispatcher = broadcastDispatcher; mScreenshotHandler = timeoutHandler; mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); mDisplayId = displayId; - mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); + mDisplayManager = displayManager; + mWindowManager = windowManager; final Context displayContext = context.createDisplayContext(getDisplay()); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); - mWindowManager = mContext.getSystemService(WindowManager.class); mFlags = flags; mActionExecutor = actionExecutor; mUserManager = userManager; @@ -363,8 +356,6 @@ public class ScreenshotController { mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); }); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - // Setup the window that we are going to use mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); mWindowLayoutParams.setTitle("ScreenshotAnimation"); @@ -390,9 +381,9 @@ public class ScreenshotController { } } }; - mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( - ClipboardOverlayController.COPY_OVERLAY_ACTION), - ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); + mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( + ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null, + Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION); mShowUIOnExternalDisplay = showUIOnExternalDisplay; } @@ -442,16 +433,6 @@ public class ScreenshotController { prepareViewForNewScreenshot(screenshot, oldPackageName); - if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - new AssistContentRequester.Callback() { - @Override - public void onAssistContentAvailable(AssistContent assistContent) { - screenshot.setContextUrl(assistContent.getWebUri()); - } - }); - } - if (!shouldShowUi()) { saveScreenshotInWorkerThread( screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, @@ -567,7 +548,7 @@ public class ScreenshotController { * Release the constructed window context. */ private void releaseContext() { - mContext.unregisterReceiver(mCopyBroadcastReceiver); + mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver); mContext.release(); } @@ -615,7 +596,7 @@ public class ScreenshotController { if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mViewProxy.getView()); } - setContentView(mViewProxy.getView()); + mWindow.setContentView(mViewProxy.getView()); } private void enqueueScrollCaptureRequest(UserHandle owner) { @@ -697,10 +678,8 @@ public class ScreenshotController { final ScrollCaptureResponse response = mLastScrollCaptureResponse; mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> { - DisplayMetrics displayMetrics = new DisplayMetrics(); - getDisplay().getRealMetrics(displayMetrics); - Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, - new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); + Bitmap newScreenshot = + mImageCapture.captureDisplay(mDisplayId, getFullScreenRect()); if (newScreenshot != null) { // delay starting scroll capture to make sure scrim is up before the app moves @@ -797,10 +776,6 @@ public class ScreenshotController { } } - private void setContentView(View contentView) { - mWindow.setContentView(contentView); - } - @MainThread private void attachWindow() { View decorView = mWindow.getDecorView(); @@ -912,12 +887,10 @@ public class ScreenshotController { public void onFinish() { } }; - Pair<ActivityOptions, ExitTransitionCoordinator> transition = - ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, - Pair.create(mViewProxy.getScreenshotPreview(), - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); - return transition; + return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, + Pair.create(mViewProxy.getScreenshotPreview(), + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); } /** Reset screenshot view and then call onCompleteRunnable */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index cb2dba00890b..65e845749f9e 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 androidx.constraintlayout.widget.ConstraintLayout; 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 @@ public class ScreenshotView extends FrameLayout implements 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/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 3169e9ccbbcb..33cf9ba4fa7c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -26,12 +26,14 @@ import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.util.kotlin.collectFlow @@ -52,6 +54,7 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, + @Communal private val dataSourceDelegator: SceneDataSourceDelegator, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ private var communalContainerView: View? = null @@ -125,6 +128,7 @@ constructor( PlatformTheme { CommunalContainer( viewModel = communalViewModel, + dataSourceDelegator = dataSourceDelegator, dialogFactory = dialogFactory, ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8d66fa77a7c3..e4f5aeb5028c 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 037dc4d6ea21..07836e44e83d 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 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl { @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 0715dfca8507..8d9fab1c83d6 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 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { 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 fc1dc62ed094..519d719d1929 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 @@ constructor( } 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 5f0b2988cd27..307e7025c153 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.ViewGroup; 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 @@ public class NotificationRemoteInputManager implements CoreStartable { // 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 c1dd992b64cd..9ce38db1aebe 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.ArrayList; 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 final class NotificationEntry extends ListEntry { 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 @@ public final class NotificationEntry extends ListEntry { */ 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 @@ public final class NotificationEntry extends ListEntry { 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 final class NotificationEntry extends ListEntry { */ 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 final class NotificationEntry extends ListEntry { } } - 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 @@ public final class NotificationEntry extends ListEntry { 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 a900e45adbe7..4ebb6998b8c7 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.InflationException 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 @@ constructor( 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 @@ constructor( 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 ded635cb08bc..31e69c98549b 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder } } - 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 5a7433d3579b..5ab58576a89d 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 @@ constructor( // 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 @@ constructor( // 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 9df6f93df6a5..9f576066cc98 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 @@ constructor( // 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 @@ constructor( goneToDreamingTransitionViewModel.lockscreenAlpha, goneToDozingTransitionViewModel.lockscreenAlpha, lockscreenToDreamingTransitionViewModel.lockscreenAlpha, - lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), + lockscreenToGoneTransitionViewModel.notificationAlpha(viewState), lockscreenToOccludedTransitionViewModel.lockscreenAlpha, lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, occludedToAodTransitionViewModel.lockscreenAlpha, @@ -589,8 +589,13 @@ constructor( 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 c19378374c20..79ea59c27090 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 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu 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 579d43b5fd05..1fc7bf467757 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.LayoutInflater; 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.res.R; 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private boolean mColorized; private int mLastBackgroundColor; private boolean mResetting; - @Nullable - private RevealParams mRevealParams; private Rect mContentBackgroundBounds; private boolean mIsAnimatingAppearance = false; @@ -465,20 +460,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mRemoved = true; } - public void setRevealParameters(@Nullable RevealParams revealParams) { - mRevealParams = revealParams; - } - @Override public void dispatchStartTemporaryDetach() { super.dispatchStartTemporaryDetach(); @@ -1178,24 +1155,4 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } - - 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 bfee9adf1f15..f61936949bb7 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.ArraySet 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 @@ interface RemoteInputViewController { /** 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 @@ interface RemoteInputViewController { other.close() remoteInput = other.remoteInput remoteInputs = other.remoteInputs - revealParams = other.revealParams pendingIntent = other.pendingIntent focus() } @@ -142,14 +138,6 @@ class RemoteInputViewControllerImpl @Inject constructor( 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 @@ class RemoteInputViewControllerImpl @Inject constructor( 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 3242c2814bc5..57b5d570fbbd 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 @@ constructor( } } - private fun AudioStreamModel.toState( + private suspend fun AudioStreamModel.toState( isEnabled: Boolean, ringerMode: RingerMode, ): State { @@ -116,7 +116,7 @@ constructor( 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 000000000000..12f334ba08bb --- /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 bd49927c8b53..41d5d5d919e2 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 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Mock private WindowMagnificationController mWindowMagnificationController; @Mock + private FullscreenMagnificationController mFullscreenMagnificationController; + @Mock private MagnificationSettingsController mMagnificationSettingsController; @Mock private ModeSwitchesController mModeSwitchesController; @@ -105,6 +107,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase { 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 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } @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 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } } + + 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 58011eb63f69..190babdb22b0 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 @@ class BackAnimationSpecTest : SysuiTestCase() { 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 f5c9befef0ef..314abda66401 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 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { 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 51828c91de4b..6ebda4db808f 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.SurfaceControl 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { 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 7bef01a7a5ce..3926f92b44e8 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.KeyguardState 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 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { } 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 258dbf3efbae..cded2a4ec27c 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.KeyguardState 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 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { } 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 000000000000..326df5c07b85 --- /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/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 07d93508228e..5ca6cf12ba90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.testKosmos @@ -104,7 +105,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { dialogFactory, keyguardTransitionInteractor, shadeInteractor, - powerManager + powerManager, + kosmos.sceneDataSourceDelegator, ) testableLooper = TestableLooper.get(this) @@ -145,6 +147,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { keyguardTransitionInteractor, shadeInteractor, powerManager, + kosmos.sceneDataSourceDelegator, ) // First call succeeds. @@ -268,7 +271,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } private fun goToScene(scene: SceneKey) { - communalRepository.setDesiredScene(scene) + communalRepository.changeScene(scene) testableLooper.processAllMessages() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 5b6da0eeb40b..e957ca2eca3a 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 6d5d5be8ae57..29a92d969ca4 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 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @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 0e89d8072a2e..06a4d0820386 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.R; 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 @@ import java.util.function.Consumer; @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/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index bc0bf9dd069f..de7b14d1e102 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -32,6 +32,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor import com.android.systemui.scene.SceneContainerFrameworkModule import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSource import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.BaseShadeInteractor @@ -45,6 +46,7 @@ import dagger.Provides import javax.inject.Provider import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -71,7 +73,10 @@ interface SysUITestModule { @Binds @Main fun bindMainResources(resources: Resources): Resources @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor - @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource + + @Binds + @SysUISingleton + fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource @Binds fun provideFaceAuthInteractor( @@ -109,6 +114,15 @@ interface SysUITestModule { sceneContainerOff.get() } } + + @Provides + @SysUISingleton + fun providesSceneDataSourceDelegator( + @Application applicationScope: CoroutineScope, + config: SceneContainerConfig, + ): SceneDataSourceDelegator { + return SceneDataSourceDelegator(applicationScope, config) + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index 5ff588f810bd..9f5c6b8faa38 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -2,6 +2,7 @@ package com.android.systemui.communal.data.repository import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.shared.model.CommunalScenes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -17,11 +18,11 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalRepository( applicationScope: CoroutineScope, - override val desiredScene: MutableStateFlow<SceneKey> = + override val currentScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default), ) : CommunalRepository { - override fun setDesiredScene(desiredScene: SceneKey) { - this.desiredScene.value = desiredScene + override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { + this.currentScene.value = toScene } private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt index c909dd6ffdd5..b943298f6b53 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 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import 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/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt index f0fedd2ed479..1e25f7fd470e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.biometrics.authController import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor val Kosmos.lockscreenContentViewModel by @@ -30,5 +31,6 @@ val Kosmos.lockscreenContentViewModel by authController = authController, longPress = keyguardLongPressViewModel, shadeInteractor = shadeInteractor, + applicationScope = applicationCoroutineScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt index 17c3a14bd5e1..7a023ee29299 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 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import 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 000000000000..b32960a73178 --- /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 d79374021968..a90a9ffc8a33 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 @@ import com.android.systemui.power.domain.interactor.powerInteractor 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 a3ad2b87d5f5..4788624bdf02 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 @@ class FakeAudioRepository : AudioRepository { 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 @@ class FakeAudioRepository : AudioRepository { 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 fb2805574ff0..1f65e15c1bff 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.SuppressLint; 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 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect 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 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect connection.setTestData(mTestBrailleDisplays); } connection.connectLocked( - bluetoothAddress, BrailleDisplayConnection.BUS_BLUETOOTH, controller); + bluetoothAddress, + bluetoothDeviceName, + BrailleDisplayConnection.BUS_BLUETOOTH, + controller); } } @@ -763,7 +775,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect 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 8b41873636a9..b0da3f014452 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.IBrailleDisplayConnection; 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.HandlerThread; 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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { @BusType int getDeviceBusType(@NonNull Path path); + + String getName(@NonNull Path path); } /** @@ -149,15 +153,19 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * <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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { 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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { 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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { 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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * @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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { 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 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { 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 993a1d5494a2..558e07f77656 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.FLAG_SMART_SUGGESTION_SYSTEM 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 253fe35a3e29..ac19d8bc897f 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -161,20 +161,20 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED"); - - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); init(getContext().getSystemService(MediaProjectionManager.class), LocalServices.getService(WindowManagerInternal.class), - getExemptedPackages()); + LocalServices.getService(PackageManagerInternal.class), + getExemptedPackages() + ); if (sensitiveContentAppProtection()) { publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE, - new SensitiveContentProtectionManagerServiceBinder(mPackageManagerInternal)); + new SensitiveContentProtectionManagerServiceBinder()); } } @VisibleForTesting void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager, - ArraySet<String> exemptedPackages) { + PackageManagerInternal packageManagerInternal, ArraySet<String> exemptedPackages) { if (DEBUG) Log.d(TAG, "init"); Objects.requireNonNull(projectionManager); @@ -182,6 +182,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic mProjectionManager = projectionManager; mWindowManager = windowManager; + mPackageManagerInternal = packageManagerInternal; mExemptedPackages = exemptedPackages; // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary @@ -231,14 +232,16 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } private void onProjectionStart(MediaProjectionInfo projectionInfo) { - int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, - projectionInfo.getUserHandle().getIdentifier()); boolean isPackageExempted = (mExemptedPackages != null && mExemptedPackages.contains( projectionInfo.getPackageName())) - || canRecordSensitiveContent(projectionInfo.getPackageName()); + || canRecordSensitiveContent(projectionInfo.getPackageName()) + || isAutofillServiceRecorderPackage(projectionInfo.getUserHandle().getIdentifier(), + projectionInfo.getPackageName()); // TODO(b/324447419): move GlobalSettings lookup to background thread boolean isFeatureDisabled = Settings.Global.getInt(getContext().getContentResolver(), DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0; + int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0, + projectionInfo.getUserHandle().getIdentifier()); mMediaProjectionSession = new MediaProjectionSession( uid, isPackageExempted || isFeatureDisabled, new Random().nextLong()); @@ -295,8 +298,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic // notify windowmanager of any currently posted sensitive content notifications ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(notifications, rankingMap); - - mWindowManager.addBlockScreenCaptureForApps(packageInfos); + if (packageInfos.size() > 0) { + mWindowManager.addBlockScreenCaptureForApps(packageInfos); + } } private ArraySet<PackageInfo> getSensitivePackagesFromNotifications( @@ -422,6 +426,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic if (!mProjectionActive) { return; } + if (DEBUG) { Log.d(TAG, "setSensitiveContentProtection - current package=" + packageInfo + ", isShowingSensitiveContent=" + isShowingSensitiveContent @@ -452,15 +457,29 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } } - private final class SensitiveContentProtectionManagerServiceBinder - extends ISensitiveContentProtectionManager.Stub { - private final PackageManagerInternal mPackageManagerInternal; + // TODO: b/328251279 - Autofill service exemption is temporary and will be removed in future. + private boolean isAutofillServiceRecorderPackage(int userId, String projectionPackage) { + String autofillServiceName = Settings.Secure.getStringForUser( + getContext().getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, userId); + if (DEBUG) { + Log.d(TAG, "autofill service for user " + userId + " is " + autofillServiceName); + } - SensitiveContentProtectionManagerServiceBinder( - PackageManagerInternal packageManagerInternal) { - mPackageManagerInternal = packageManagerInternal; + if (autofillServiceName == null) { + return false; + } + ComponentName serviceComponent = ComponentName.unflattenFromString(autofillServiceName); + if (serviceComponent == null) { + return false; } + String autofillServicePackage = serviceComponent.getPackageName(); + return autofillServicePackage != null + && autofillServicePackage.equals(projectionPackage); + } + + private final class SensitiveContentProtectionManagerServiceBinder + extends ISensitiveContentProtectionManager.Stub { public void setSensitiveContentProtection(IBinder windowToken, String packageName, boolean isShowingSensitiveContent) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index c18bacb51671..72a55dbea481 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -177,6 +177,7 @@ public class Watchdog implements Dumpable { "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 ed1a763b1512..b00676a15c0d 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * 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 @@ public class ActivityManagerService extends IActivityManager.Stub } @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 c2613bc47eb2..60a8b50feeab 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 @@ class UserController implements Handler.Callback { } 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 @@ class UserController implements Handler.Callback { 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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 649b9efc3282..be47f8581db3 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -46,6 +46,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import static com.android.media.audio.Flags.alarmMinVolumeZero; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; +import static com.android.media.audio.Flags.setStreamVolumeOrder; import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -4538,6 +4539,8 @@ public class AudioService extends IAudioService.Stub + focusFreezeTestApi()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); + pw.println("\tcom.android.media.audio.setStreamVolumeOrder:" + + setStreamVolumeOrder()); pw.println("\tandroid.media.audio.foregroundAudioControl:" + foregroundAudioControl()); } @@ -4705,6 +4708,30 @@ public class AudioService extends IAudioService.Stub index = rescaleIndex(index * 10, streamType, streamTypeAlias); + if (setStreamVolumeOrder()) { + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + + // volume is either 0 or max allowed for fixed volume devices + if (index != 0) { + index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); + if (index < 0) { + index = streamState.getMaxIndex(); + } + } + } + + if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, + flags)) { + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, + // ada is non-null when called from setDeviceVolume, + // which shouldn't update the mute state + canChangeMuteAndUpdateController /*canChangeMute*/); + index = mStreamStates[streamType].getIndex(device); + } + } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { @@ -4738,26 +4765,28 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } - flags &= ~AudioManager.FLAG_FIXED_VOLUME; - if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { - flags |= AudioManager.FLAG_FIXED_VOLUME; + if (!setStreamVolumeOrder()) { + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; - // volume is either 0 or max allowed for fixed volume devices - if (index != 0) { - index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); - if (index < 0) { - index = streamState.getMaxIndex(); + // volume is either 0 or max allowed for fixed volume devices + if (index != 0) { + index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); + if (index < 0) { + index = streamState.getMaxIndex(); + } } } - } - if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, - flags)) { - onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, - // ada is non-null when called from setDeviceVolume, - // which shouldn't update the mute state - canChangeMuteAndUpdateController /*canChangeMute*/); - index = mStreamStates[streamType].getIndex(device); + if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, + flags)) { + onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, + // ada is non-null when called from setDeviceVolume, + // which shouldn't update the mute state + canChangeMuteAndUpdateController /*canChangeMute*/); + index = mStreamStates[streamType].getIndex(device); + } } synchronized (mHdmiClientLock) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 641b6a2f170b..61ecb934dd38 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 @@ public class DisplayDeviceConfig { * * @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 a577e225076f..1dfe03735595 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 class LocalDisplayAdapter extends DisplayAdapter { 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 9c7504db0cf0..a46975fb3567 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 @@ public class BrightnessClamperController { 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 85ab77355c9a..1c14fc1b08dd 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 @@ public final class ImeTrackerService extends IImeTracker.Stub { } } + @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 095a233bde64..4da280bf5c7b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2446,7 +2446,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService ComponentName intentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr(computer); ComponentName domainVerificationAgent = - getDomainVerificationAgentComponentNameLPr(computer); + getDomainVerificationAgentComponentNameLPr(computer, UserHandle.USER_SYSTEM); DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy( intentFilterVerifierComponent, domainVerificationAgent, mContext, @@ -2754,12 +2754,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Nullable - private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer) { + private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer, + int userId) { Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION); List<ResolveInfo> matches = mResolveIntentHelper.queryIntentReceiversInternal(computer, intent, null, MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, - UserHandle.USER_SYSTEM, Binder.getCallingUid()); + userId, Binder.getCallingUid()); ResolveInfo best = null; final int N = matches.size(); for (int i = 0; i < N; i++) { @@ -2767,7 +2768,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService final String packageName = cur.getComponentInfo().packageName; if (checkPermission( android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, packageName, - UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) { + userId) != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Domain verification agent found but does not hold permission: " + packageName); continue; @@ -2775,7 +2776,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (best == null || cur.priority > best.priority) { if (computer.isComponentEffectivelyEnabled(cur.getComponentInfo(), - UserHandle.SYSTEM)) { + UserHandle.of(userId))) { best = cur; } else { Slog.w(TAG, "Domain verification agent found but not enabled"); @@ -6272,9 +6273,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService final int[] userIds = resolveUserIds(UserHandle.USER_ALL); final String reason = "The mimeGroup is changed"; for (int i = 0; i < userIds.length; i++) { - final int packageUid = UserHandle.getUid(userIds[i], appId); - mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName, - true /* dontKillApp */, components, packageUid, reason); + final PackageUserStateInternal pkgUserState = + packageState.getUserStates().get(userIds[i]); + if (pkgUserState != null && pkgUserState.isInstalled()) { + final int packageUid = UserHandle.getUid(userIds[i], appId); + mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName, + true /* dontKillApp */, components, packageUid, reason); + } } }); } @@ -6508,13 +6513,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override @Nullable - public ComponentName getDomainVerificationAgent() { + public ComponentName getDomainVerificationAgent(int userId) { final int callerUid = Binder.getCallingUid(); if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) { throw new SecurityException("Not allowed to query domain verification agent"); } final Computer snapshot = snapshotComputer(); - return getDomainVerificationAgentComponentNameLPr(snapshot); + return getDomainVerificationAgentComponentNameLPr(snapshot, userId); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a9e1725ea9a0..59faf24aa77a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4412,8 +4412,31 @@ class PackageManagerShellCommand extends ShellCommand { private int runGetDomainVerificationAgent() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) { + UserManagerInternal umi = + LocalServices.getService(UserManagerInternal.class); + UserInfo userInfo = umi.getUserInfo(userId); + if (userInfo == null) { + pw.println("Failure [user " + userId + " doesn't exist]"); + return 1; + } + } + } else { + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + final int translatedUserId = + translateUserId(userId, UserHandle.USER_SYSTEM, "runGetDomainVerificationAgent"); try { - final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent(); + final ComponentName domainVerificationAgent = + mInterface.getDomainVerificationAgent(translatedUserId); pw.println(domainVerificationAgent == null ? "No Domain Verifier available!" : domainVerificationAgent.flattenToString()); } catch (Exception e) { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 8e3c6ac799b4..3a84897839a1 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 class BatteryStatsImpl extends BatteryStats { 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 17e699668d14..2ec26ca710ae 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } newIntents = null; - if (isActivityTypeHome()) { - mTaskSupervisor.updateHomeProcess(task.getBottomMostActivity().app); - } + mTaskSupervisor.updateHomeProcessIfNeeded(this); if (nowVisible) { mTaskSupervisor.stopWaitingForActivityVisible(this); @@ -8507,8 +8505,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || (!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 07f52574395c..430232c1bfc4 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { + " 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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 933633836e56..47f4a66995af 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 @@ public class BackgroundActivityStartController { 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 21326be0734c..8c4f9ef41fd6 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.InsetsSourceConsumer; 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 class ImeInsetsSourceProvider extends InsetsSourceProvider { 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 b89c12bca62f..6f1c834be039 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 @@ class Task extends TaskFragment { } } + @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 c3375236098a..180081c9173a 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 android { 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 @@ static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawB 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 @@ static const JNINativeMethod gMethods[] = { (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/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java index 2366f56707fa..7aafa8e92690 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java @@ -24,9 +24,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Binder; @@ -72,6 +74,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { @Mock private WindowManagerInternal mWindowManager; @Mock private MediaProjectionManager mProjectionManager; + @Mock private PackageManagerInternal mPackageManagerInternal; private MediaProjectionInfo mMediaProjectionInfo; @Captor @@ -91,7 +94,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { mSensitiveContentProtectionManagerService = new SensitiveContentProtectionManagerService(mContext); mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager, - new ArraySet<>(Set.of(mExemptedScreenRecorderPackage))); + mPackageManagerInternal, new ArraySet<>(Set.of(mExemptedScreenRecorderPackage))); verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue(); mMediaProjectionInfo = @@ -146,6 +149,20 @@ public class SensitiveContentProtectionManagerServiceContentTest { } @Test + public void testAutofillServicePackageExemption() { + String testAutofillService = mScreenRecorderPackage + "/com.example.SampleAutofillService"; + int userId = Process.myUserHandle().getIdentifier(); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, testAutofillService , userId); + + mMediaPorjectionCallback.onStart(mMediaProjectionInfo); + mSensitiveContentProtectionManagerService.setSensitiveContentProtection( + mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); + verify(mWindowManager, never()) + .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + } + + @Test public void testDeveloperOptionDisableFeature() { mockDisabledViaDeveloperOption(); mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo); diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index e74fe296d0a5..506514469338 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -104,6 +105,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { private WindowManagerInternal mWindowManager; @Mock + private PackageManagerInternal mPackageManagerInternal; + + @Mock private StatusBarNotification mNotification1; @Mock @@ -141,7 +145,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { setupSensitiveNotification(); mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager, - new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE))); + mPackageManagerInternal, new ArraySet<>(Set.of(EXEMPTED_SCREEN_RECORDER_PACKAGE))); // Obtain useful mMediaProjectionCallback verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any()); diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 99752212fcbd..ce5cee0b6113 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.content.pm.PackageManagerInternal; 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.RemoteException; 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.ArraySet; 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 com.android.server.usage.AppStandbyInternal; 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.Mock; 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 @@ public final class AlarmManagerServiceTest { private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback; private IAppOpsCallback mIAppOpsCallback; private IAlarmManager mBinder; + private File mTestDir; @Mock private Context mMockContext; @Mock @@ -413,6 +420,7 @@ public final class AlarmManagerServiceTest { .mockStatic(PermissionManagerService.class) .mockStatic(ServiceManager.class) .mockStatic(SystemProperties.class) + .mockStatic(Environment.class) .spyStatic(UserHandle.class) .afterSessionFinished( () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class)) @@ -429,7 +437,8 @@ public final class AlarmManagerServiceTest { */ 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { } @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 @@ public final class AlarmManagerServiceTest { } @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 000000000000..5d3e4994d718 --- /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 344e2c21f0a5..69a98ace1c33 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.verify; 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 @@ public class BrailleDisplayConnectionTest { } @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 @@ public class BrailleDisplayConnectionTest { 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 @@ public class BrailleDisplayConnectionTest { 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 @@ public class BrailleDisplayConnectionTest { 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 363ae141e512..21251c33569c 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 @@ public class BackNavigationControllerTests extends WindowTestsBase { }).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 85172e08423e..856ad2a02444 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 @@ public class SizeCompatTests extends WindowTestsBase { } @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 a88680a002b9..1ca808f4153a 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 @@ public class TaskTests extends WindowTestsBase { 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 55a00fc0ec89..48fc2dc4bada 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 @@ public class WindowContainerTests extends WindowTestsBase { 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 df32fbd2e58d..de6dad3b1672 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8013,6 +8013,27 @@ public class CarrierConfigManager { KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL = KEY_PREFIX + "scan_limited_service_after_volte_failure_bool"; + /** + * This config defines {@link ImsReasonInfo} code with which the emergency call + * shall be retried. + * + * <p> + * If the reason code is one of the following, the emergency call shall be retried + * regardless of this configuration. + * <ul> + * <li>{@link ImsReasonInfo#CODE_LOCAL_CALL_CS_RETRY_REQUIRED}</li> + * <li>{@link ImsReasonInfo#CODE_LOCAL_NOT_REGISTERED}</li> + * <li>{@link ImsReasonInfo#CODE_SIP_ALTERNATE_EMERGENCY_CALL}</li> + * </ul> + * <p> + * + * This config is empty by default. + * + * @hide + */ + public static final String KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY = + KEY_PREFIX + "ims_reasoninfo_code_to_retry_emergency_int_array"; + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false); @@ -8085,6 +8106,8 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL, true); defaults.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL, false); + defaults.putIntArray(KEY_IMS_REASONINFO_CODE_TO_RETRY_EMERGENCY_INT_ARRAY, + new int[0]); return defaults; } @@ -9854,6 +9877,19 @@ public class CarrierConfigManager { "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 +11033,7 @@ public class CarrierConfigManager { 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 be6bea6b7fea..a012dca19904 100644 --- a/tools/app_metadata_bundles/Android.bp +++ b/tools/app_metadata_bundles/Android.bp @@ -5,6 +5,7 @@ package { // 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 @@ java_binary_host { "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 fb7a6ab42d95..d7edfd44019c 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 @@ public class Main { 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 @@ public class Main { 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 @@ public class Main { 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 bc8063ef7b5f..cdb559b52c0e 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 7e7fcf9c08ba..3dc725b5452b 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 class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android 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 000000000000..f94b6591cd10 --- /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 000000000000..26d94c16c7f0 --- /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 000000000000..9dd55314e844 --- /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 e5ed63b74ebf..b9e06fbdfc7e 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 @@ public class DataCategory implements AslMarshallable { 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 d9463452d7bc..ae7b603c87f6 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 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> 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 d2fffc0a36f6..96ec93c28c87 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 @@ public class DataLabels implements AslMarshallable { 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 1adb140f446d..0e14ebf4eab9 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 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { @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 5ba29757e19e..cecee39cbb12 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 @@ import java.util.Set; 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 @@ public class DataType implements AslMarshallable { 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 @@ public class DataType implements AslMarshallable { 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 class DataType implements AslMarshallable { 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 @@ public class DataType implements AslMarshallable { 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 e3d1587d860c..bfa330334487 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 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> { .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 000000000000..44a5b129e428 --- /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 000000000000..4961892b10c3 --- /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 f06522fc2a5c..40ef48dc5334 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 class SafetyLabels implements AslMarshallable { 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 80b9f5783b9d..ab81b1d56033 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 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> @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 000000000000..93d9c2b080c5 --- /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 000000000000..c8c1c7beba24 --- /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 000000000000..88717b9568b8 --- /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 000000000000..13a7eb62fedd --- /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 3bc9ccc2138b..cc8fe79cb579 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.Element; 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 class XmlUtils { 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 class XmlUtils { 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 class XmlUtils { 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 @@ public class XmlUtils { } /** 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 @@ public class XmlUtils { 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 000000000000..7d5421545091 --- /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 000000000000..7ebb7a1c44bf --- /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 000000000000..3026f8bec2ed --- /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 000000000000..fe020558074d --- /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 000000000000..ba6327e9b200 --- /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 000000000000..202cc1e1c526 --- /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 000000000000..fbcb4fb2f1a4 --- /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 000000000000..202cc1e1c526 --- /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 |